20251217 - Yearn 攻击事件2:协议授人以柄错设地址,黑客自断一臂巧控价格
背景信息
20251217,Yearn 距离上次被攻击不到一个月,又被攻击了。这次被攻击的是自动化收益优化金库 yTUSD。攻击的原因是因为 yTUSD 协议错误配置了相关的衍生品代币地址,导致攻击者可以通过该代币操纵 share 价格,套取超额的 yTUSD 代币。
项目背景
yTUSD 是一个自动化收益优化金库(Yield Vault)专门用于 TUSD(TrueUSD)稳定币的收益优化。
主要功能:
- 用户存入 TUSD,获得 yTUSD 代币作为存款凭证
- 合约自动将资金分配到收益最高的 DeFi 借贷协议中
- 随着时间推移,yTUSD 的价值会因利息累积而增长
合约集成了 4 个主流借贷协议:
- dYdX - 去中心化保证金交易协议
- Compound - 去中心化借贷协议
- Aave - 去中心化借贷协议
- Fulcrum (bZx) - 保证金交易与借贷协议
Trace 分析
攻击者首先进行了一大堆的代币兑换,在一堆的衍生品代币之间换过来换过去,感兴趣的读者可以深入查看。主要执行攻击的环节在 trace 的8370-10485 之间,攻击者在闪电贷的 callback 函数中进行了这次攻击。

通过 UniswapV3 闪电贷出 165305 sUSDe,调用 uniswapV3SwapCallback
- 通过 sUSD+sUSDe 把 165305 sUSDe 兑换成 215192 sUSD
- 通过 iSUSD 把 2125192 sUSD 兑换成 213848 iSUSD
- 通过 yTUSD.deposit 将 1169030 TUSD 兑换成 732294 yTUSD
- 把 213848 iSUSD 发送到 yTUSD
- 通过 yTUSD.withdraw 销毁 769318 yTUSD 赎回 1414919 TUSD
- 调用 yTUSD.rebalance 销毁 yTUSD 持有的 213848 iSUSD,赎回 215192 sUSD
- 发送 0.00000001 TUSD 给 yTUSD
- 通过 yTUSD.deposit 将 1000 TUSD 兑换成 117004400475278030 yTUSD
代码分析
在用户 yTUSD 协议进行 deposit/withdraw 操作时,会通过 _calcPoolValueInToken() 来计算兑换的比例。
_calcPoolValueInToken() 函数
_calcPoolValueInToken() 函数用于计算以 TUSD 计价的整个金库的总价值

返回值为以下内容的总和:
- 存入 Compound 的 TUSD 价值
- 存入 Fulcrum 的 TUSD 价值
- 存入 dYdX 的 TUSD 价值
- 存入 Aave 的 TUSD 价值
- 合约中持有的 TUSD
其中 _balanceFulcrumInToken() 函数是通过计算 yTUSD 持有多少对应 iToken 的数量,再乘上 rate 来得出对应的 TUSD 价值。

但是!本该是 Fulcrum 对应 TUSD 的代币,被错误设置为了 iSUSD 代币。
而 iSUSD 代币是 Fulcrum 协议对应 sUSD 代币的生息代币:

协议背景

在进行 deposit 之前,yTUSD 的状态是:
- 持有 41500 TUSD
- 在 AAVE 中存有 204388 TUSD
- 其他 DeFi 持有的 TUSD 数量为 0
此时 yTUSD 投资的协议(provider)为 AAVE
_calcPoolValueInToken() = 245889225254854708695146
通过 yTUSD.deposit 将 1169030 TUSD 兑换成 732294 yTUSD
_calcPoolValueInToken() = 1414919509067819317145700
- 存入 Aave 的 TUSD 价值 = 1210531250593133305574099
- 合约中未投资的 TUSD = 204388258474686011571601
把 213848 iSUSD 发送到 yTUSD
After transfer, _calcPoolValueInToken() = 1630112440857309166690190
这一步的目的是为了抬高 yTUSD 的价值,_calcPoolValueInToken() 的值增加了约 461082.1570443446 * 1e18,此时每个 yTUSD 对应的资产价值将会增加。
通过 yTUSD.withdraw 销毁 769318 yTUSD 赎回 1414919 TUSD
_calcPoolValueInToken() = 1630112440857309166690190
此时 _calcPoolValueInToken() 的值已经被转入的 iSUSD 所操纵,所以能够 withdraw 更多的 TUSD,目的就是将 yTUSD 持有的 TUSD 和 AAVE 中的 TUSD 全部取走,为价格操纵做准备。
- 转账 iSUSD 前: TUSD / yTUSD = 1.59
- 转账 iSUSD 后: TUSD / yTUSD = 1.83
赎回的数量刚刚好等于 deposit 后的总价值 1414919509067819317145700

此时 withdraw 的 TUSD 数量大于 yTUSD 合约所持有的数量,触发 _withdrawSome() 函数从 AAVE 中赎回所有 TUSD。

在 withdraw 后,yTUSD 持有的 TUSD 和 AAVE 中的 TUSD 都已经被取走,剩下的只有合约中持有 iSUSD 对应的“虚假 TUSD”数量。

调用 yTUSD.rebalance 销毁 yTUSD 持有的 213848 iSUSD,赎回 215192 sUSD
在 rebalance() 函数中,由于返回的都是 apr 都为 0,所以返回的是默认的 COMPOUND。

由于与目前的 AAVE 不一致,所以会触发调仓操作,调用 _withdrawAll() 函数将所有 DeFi 中的资金全部赎回到 yTUSD 合约。

因为 AAVE 中的代币已经在 withdraw 时被全部赎回,此时只从 iSUSD 处赎回了 sUSD 代币,其余 DeFi 中都没有持有 yTUSD 代币。
这一步将 iSUSD 销毁赎回 sUSD 的操作,相当于凭空销毁了对应的 TUSD 价值。因为赎回后发送到 yTUSD 合约的 sUSD 并不会被当作 TUSD 计数,而是“透明地”被锁定在了合约里无法取回。
最后检查 yTUSD 合约的 TUSD 余额为 0 ,没有调用 supply 函数进行再投资。

发送 0.00000001 TUSD 给 yTUSD
_calcPoolValueInToken() = 0
此时向 yTUSD 发送 0.00000001 TUSD,目的是操纵抬高 TUSD 的价格,通过少量的 TUSD + 大量的 yTUSD totalSupply,计算出 TUSD 的价格大幅提高。如果不转入少量的 TUSD,在 pool = 0 时将会会按照 1:1 的比例来提供 shares。
通过 yTUSD.deposit 将 1000 TUSD 兑换成 117004400475278030 yTUSD
_calcPoolValueInToken() = 1000000000
由于 yTUSD 中只有少量的 TUSD,以及之前 mint 的大量 yTUSD,所以在计算 deposit 对应的 shares 会得到一个很大的值。黑客在这个环节获取到了超额的 yTUSD 代币,完成获利。
- _totalSupply = 117004400475278030262758

- shares = 117004400475278030

归还闪电贷+转移资产
在获得了大量的 yTUSD 后,黑客将其进行抛售,归还闪电贷,并转移走剩余的 TUSD 和 USDC。


浙公网安备 33010602011771号