概述
2023 年 4 月 13 日,Yearn Finance 遭到黑客攻击,导致大约损失 1000 万美元。本文将分析攻击过程以及漏洞产生的原因。
攻击分析
这是一笔攻击交易:
https://etherscan.io/tx/0xd55e43c1602b28d4fd4667ee445d570c8f298f5401cf04e62ec329759ecda95d
攻击者从 Balancer 发起了闪电贷,借了 500 万 DAI、 500 万 USDC 和 200 万 USDT:
然后在 Curve 上,攻击者将 500 万 DAI 兑换成了 695, 000 USDT,并将 350 万 USDC 兑换成 151 USDT:
攻击者调用 IEarnAPRWithPool 的recommend函数来检查当前的 APR。此时,只有 Aave 的 APR 不等于 0 :
接下来,攻击者将 800, 000 USDT 转移到了攻击合约 0x9fcc1409b56cf235d9cdbbb86b6ad5089fa0eb0f 中。在该合约中,攻击者多次调用了 Aave:Lending Pool V1的repay函数,帮助其他人偿还债务,以使 Aave 的 APR 等于 0 :
攻击者调用了 yUSDT 的deposit函数,抵押了 900, 000 USDT,并获得了 820, 000 yUSDT:
接下来,攻击者调用了 bZx iUSDC 的mint函数,使用 156, 000 USDC 铸造了 152, 000 bZx iUSDC,并将其转移到了 Yearn yUSDT:
攻击者调用 Yearn:yUSDT 的withdraw函数,将 820, 000 yUSDT 兑换成 1, 030, 000 USDT。此时,合约中只剩下攻击者转移的 bZx iUSDC:
接下来攻击者调用 Yearn:yUSDT 的rebalance函数,销毁 bZx iUSDC:
然后攻击者向 yUSDT 合约转移了 1/e 6 个 USDT,并调用了deposit函数,抵押了 10, 000 USDT,获得了 1, 252, 660, 242, 850, 000 yUSDT:
然后在 Curve 上,攻击者将 70, 000 yUSDT 兑换成 5, 990, 000 yDAI,将 4 亿 yUSDT 兑换成 4, 490, 000 yUSDC,将 1, 240, 133, 244, 352, 200 yUSDT 兑换成 1, 360, 000 yTUSD:
然后在 yearn: yDAI 和 yearn: yUSDC 中分别调用 withdraw ,提取 678 万 个 DAI 和 562 w 万个 USDC,并归还闪电贷:
漏洞分析
这次攻击中最关键的一点,是攻击者使用 100, 000 USDT 铸造了 1, 252, 660, 242, 850, 000 个 yUSDT。查看 deposit 函数的实现:
可以看到 share 的数量和变量 pool 相关,pool 越小,share 越大,而 pool 的值由 _calcPoolValueInToken 获得:
攻击者在调用 rebalance 函数 后,合约中只存在了 USDC,但是 _balance() 获取的是 USDT 的余额,USDC 的余额并不计入其中,因此此时的 pool 为 1 (攻击者转入的) :
这里显然是项目方的配置错误,yUSDT 合约中 应当都是 USDT 类的代币,但是其 fulcrum 变量却是 USDC 相关的 bZx IUSDC 代币,因此 yUSDT 中的 USDC 不计入 balance 中:
攻击者为什么能调用 rebalance 函数来 burn 掉 bZx iUSDC 代币呢?查看 rebalance 函数的实现:
可以看到在 _withdrawFulcrum() 中会 存在 redeem 和 burn 操作,因此我们需要让 newProvider != provider 成立 , 其中 recommend() 的实现:
攻击者通过控制 IIEarnManager(apr).recommend(token) 的返回值,使其为都为 0 来操控 newProvider:
如何让其都为 0 呢,该函数的返回值和计算出的各个 DeFi 中的 APR 相关,由于 Compound,bZx,dydx 中没有池子,因此只需要控制 Aave ( Aave: Lending Pool Core V1) 即可:
要使其值返回为 0 ,需要让 apr.calculateInterestRates 函数的第一个返回值为 0 :
即让 currentLiquidityRate 为 0 ,该值和 _totalBorrowsStable、_totalBorrowsVariable 相关,当这两个个值都为 0 时,currentLiquidityRate 为 0 :
_totalBorrowsVariable 为 0 ,即 Aave: Lending Pool Core V1 此时没有人存在债务,为了达成这个条件,攻击者将池中所有人的债务进行了 repay :
最后,攻击者让 _totalBorrowsVariable 变为 0 ,所以它能够调用 rebalance 函数 burn 掉 bZx iUSDC 代币:
总结
此次 Yearn 攻击事件的根本原因是项目方的配置错误。攻击者通过一系列精妙的手法利用了该漏洞,最终获利大约 1000 万美元。
关于我们
At Eocene Research, we provide the insights of intentions and security behind everything you know or dont know of blockchain, and empower every individual and organization to answer complex questions we hadnt even dreamed of back then.