前言
2025 年 12 月 1 日,Yearn 协议遭遇了一起精心设计的多阶段复杂黑客攻击,最终导致约 900 万美元资产损失。攻击并非单一漏洞利用,而是攻击者通过闪电贷撬动资金、利用协议多重逻辑缺陷逐步操控资金池状态,最终实现近乎无限铸造 yETH 相关 LP 代币并耗尽资金池的恶性事件。
本次攻击以闪电贷为初始资金杠杆,通过多步骤操作层层突破协议防护,核心攻击流程可分为资金筹备、状态操控、无限铸币及获利了结四个关键阶段,各环节环环相扣,精准利用了协议设计中的逻辑漏洞。
攻击 tx: https://etherscan.io/tx/0x53fe7ef190c34d810c50fb66f0fc65a1ceedc10309cf4b4013d64042a0331156
技术分析
首先通过 2 个闪电贷分别从 Balancer 和 Aave 中借出 wstETH, rETH, WETH 和 0xa35b_ETHx, rETH, wstETH, cbETH。


在闪电贷回调函数中将借出的 ETH 用 1100 ETH 存入 Tornado.Cash: 100 ETH 混币,随后借助 Tornado.Cash: 100 ETHwithdraw 函数取款 100 ETH 到恶意合约 0x3e8e7533dcf69c698Cf806C3DB22f7f10B9B0b97 并触发其 fallback 函数。


在 fallack 函数中,可以看到下图最后一笔 exchange 与下一步 remove_liquidity 数值一致,可以推断前面的步骤都是为了将闪电贷得到的资产都通过 exchange 等操作兑换成大量的 yETH weighted stableswap pool 的 LP 代币,为后续攻击做准备。

至此,核心的攻击流程正式开始。
1.首先将上述 exchange 得到的 LP 代币通过 yETH weighted stableswap pool 的 remove_liquidity 函数全部销毁转化成按份额分配的 8 种该 pool 中的底层资产。
该 remove_liquidity 函数的逻辑可以理解为假设池子总共有 10,000 个 LP Token,你销毁了 416.37 个 LP Token,你的比例就是:416.37 / 10,000 = 4.16%。
然后对于每一种资产,假设池子里有 1,000 个 wstETH(虚拟余额 prev_vb)。你能取走的虚拟余额:dvb = 1,000 * 416.37 / 10,000 = 41.637 wstETH,然后再除以汇率 rate 转换为实际代币数量。
2.其次通过反复调用 add_liquidity 类似注入单边池子,一共有 8 个 _amounts 参数分别对应要注入的不同资产的数量。观察到前面的几次循环 index3[rETH 代币]、index6[wOETH 代币]和 index7[mETH 代币]都输入 0,即每次加流动性都不加这 3 个代币。

通过如上方式注入单边资产并提取池中代币的方式人为地扩大池子中 rETH、w0 ETH 和 mETH 和其他代币的数量差距。
3.紧接着单边注入巨量的 rETH 代币,而后关键的一步 remove_liquidity,但是输入 _amount=0。

为什么可以提取 0 个代币?通过remove_liquidity的内部实现。

remove_liquidity 函数没有对 0⾦额进⾏短路处理,依然执⾏了完整的 vb_prod 计算循环,并且是基于上述人为制造的池子中代币数量差距计算并更新全局 packed_pool_vb 状态。
4.而后调用 update_rates 函数只更新 index6[wOETH]的池子比例,最后再调用 remove_liquidity 提取池中代币,此时该 pool 中的 W0 ETH 数量已经所剩无几。

5.同样的通过类似的方式榨干 pool 中 index6[w0 ETH]和 index7[mETH]的份额,注意这里前两次更新 index6 后马上 remove_liquidity 提取代币,而最后一个 index7[mETH]目前只做了更新还未提取。

至此通过上述加巨量单边池并不断的提取所有池子代币的方式,pool 中 W0 ETH 和 mETH 的比例已经近乎为 0。
此时创建新的恶意合约0xADbE952eBB9b3e247261d2E3b96835f00f721f8E,并将所有的代币转到该合约。注意在这之前的上一步骤中单边加 rETH 获得的 LP 代币并没有兑换成底层代币,而是也转移到了新的恶意合约。

前面的攻击操作在 update_rates 更新了 index7[mETH]而未做提取的代币在这里通过调用 remove_liquidity 提取,此时 pool 中 index6[w0 ETH]占比份额很小,index7[mETH]的占比份额更小。

此时 pool 中代币比例已经严重失调,攻击者再次调用 add_liquidity 添加流动性,按照[1, 1, 1, 1, 1, 1, 1, 9]的比例获取巨量 LP 代币。

到这里攻击者已经获得大额 LP 代币,后续便是通过 exchange、redeem 等方式获利了结并偿还闪电贷的费用。

攻击复盘
本次攻击是一起复杂的多阶段组合攻击,攻击者利用了 pool.vy 合约中的三个核心漏洞:精度丢失 (Precision Loss)、收益剥离 (Yield Stripping) 和零供应初始化 (Zero Supply Initialization)。
阶段一:制造极端失衡
- 操作: 攻击者反复调用 add_liquidity,但刻意避开 index 3 (rETH), index 6 (wOETH), index 7 (mETH)。
- 目的: 人为制造池子中资产比例的失衡。
- 关键步骤: 而后单边注入巨量 rETH。
- 后果: 极度拉大 rETH 与其他资产(特别是 wOETH 和 mETH)的数量差距,为精度丢失创造数学条件。
阶段二:触发并锁定误差
- 操作: 调用 remove_liquidity(_amount=0)。
- 原理:
remove_liquidity 未对 0 金额进行短路处理。
即使不转账,合约依然执行 vb_prod 的完整计算循环。
在极端权重失衡下,_pow_down 函数产生显著的向下取整误差。
合约将这个错误偏小的 vb_prod 写入全局状态 packed_pool_vb。
- 本质: 这是一个“零成本”的状态攻击,攻击者未付出任何代价就成功篡改了池子的账面价值。
阶段三:收益剥离与份额榨取
- 操作:
update_rates([6]) (更新 wOETH 汇率)。
remove_liquidity (提取资产)。
update_rates([7]) (更新 mETH 汇率)。
- 原理:
update_rates 会触发 _update_supply。由于 vb_prod 之前被恶意压低,系统误判池子价值缩水,从而销毁 Staking 合约持有的 LP 代币来平账。
攻击者利用 remove_liquidity 在汇率更新前后进行套利,逐步榨干池子中 wOETH 和 mETH 的份额。
- 结果: Staking 合约的份额被大量销毁,攻击者手中的 LP 份额占比被动提升,池子 Total Supply 被推向 0。
阶段四:零供应无限铸币
- 前置状态: 经过上述操作,池子已被掏空,Total Supply 接近 0,且 wOETH 和 mETH 余额极低。
- 操作: add_liquidity,参数为 _amounts=[1, 1, 1, 1, 1, 1, 1, 9]。
- 原理:
当 prev_supply ≈ 0 时,_calc_supply 的迭代公式在处理极小数值(1 wei, 9 wei)时失效。
合约错误地计算出了天文数字的 LP Token 铸造量。
- 结果: 攻击者凭空获得了235,443... 个 yETH LP Token。
总结
Yearn 本次攻击事件暴露了 DeFi 协议在边缘场景逻辑校验、数值计算精度控制及多漏洞组合风险防控上的多重不足。攻击者以闪电贷为工具、漏洞组合利用为核心、资金混淆为掩护的攻击模式,凸显了当前 DeFi 攻击的专业化、复杂化趋势。本次攻击的核心教训包括:一是协议需强化对"零金额""极端失衡"等边缘场景的逻辑校验,避免因未短路处理产生状态篡改风险;二是数值计算中需重视极端比例下的精度损失问题,优化 _pow_down 等关键函数的计算逻辑,此前 Balancer 协议就曾因精度损失出现安全事件,这已是前车之鉴;三是应建立多维度风险监控体系,对高频单边流动性注入、异常汇率更新等可疑操作进行预警。对于整个 DeFi 行业而言,本次事件再次证明,协议安全不仅需要单个漏洞的修复,更需从全流程视角防范多漏洞组合利用的攻击,同时加强对攻击者资金流向的追踪与拦截,提升行业整体安全防护能力。
免责声明:本文章仅代表作者个人观点,不代表本平台的立场和观点。本文章仅供信息分享,不构成对任何人的任何投资建议。用户与作者之间的任何争议,与本平台无关。如网页中刊载的文章或图片涉及侵权,请提供相关的权利证明和身份证明发送邮件到support@aicoin.com,本平台相关工作人员将会进行核查。
