事件摘要
北京时间 4月18日 08:58,黑客利用Uniswap和ERC777的兼容性问题,在进行 ETH-imBTC 交易时,利用ERC777中的多次迭代调用tokensToSend来实现重入攻击,将Uniswap上的imBTC(imBTC是一个1:1锚定比特币的ERC-20代币)池耗尽。
4月19日 09:28,Lendf.me 遭遇类似 Uniswap 事件的重入攻击,出现大量异常借贷行为,攻击者利用重入漏洞覆盖自己的资金余额并使得可提现的资金量不断翻倍,黑客以滚雪球的方式多笔转走imBTC,且每一笔都比上一笔翻倍,最终将Lendf.Me账户资产盗取一空。
随后Lendf.Me官方在 Lendf.Me 用户界面用红字提醒,呼吁用户目前不要向合约存款,截至发稿, Lendf.Me 已经停止服务,正在调查处理中。
此次黑客攻击,共累计的损失约24,696,616美元,具体盗取的币种及数额为:
WETH:55159.02134,
WBTC:9.01152,
CHAI:77930.93433,
HBTC:320.27714,
HUSD:432162.90569,
BUSD:480787.88767,
PAX:587014.60367,
TUSD:459794.38763,
USDC:698916.40348,
USDT:7180525.081569999,
USDx:510868.16067,
imBTC:291.3471
攻击成功后,攻击者不断通过OneInchExchange、AugustusSwapper、Tokenlon等去中心化交易平台以及Compound 借贷平台将盗取的币进行兑换和转移。
攻击过程复盘
本次针对 Lendf.Me 实施攻击的攻击者地址为: 0xa9bf70a420d364e923c74448d9d817d3f2a77822,攻击者的合约地址为: 0x538359785a8d5ab1a741a0ba94f26a800759d91d ,攻击者通过此合约对 Lendf.Me 进行攻击。如下图所示:
下面我们以攻击者盗取 Lendf.Me 账户中的 imBTC 资产为例,详细分析黑客攻击过程。
第一笔攻击交易
发起的第一笔交易是:https://cn.etherscan.com/tx/0xe49304cd3edccf32069dcbbb5df7ac3b8678daad34d0ad1927aa725a8966d52a,
通过bloxy查看调用过程,https://bloxy.info/tx/0xe49304cd3edccf32069dcbbb5df7ac3b8678daad34d0ad1927aa725a8966d52a
第一次调用supply函数攻击者存入0.00021594,正常执行调用过程
第二次调用supply函数攻击者存入0.00000001,此时进行重入攻击,合约执行流程进入withdraw函数,攻击者调用withdraw函数取出攻击者的余额记录0.00021594,然后合约执行流程在回到supply函数,完成存入操作
最后交易持续完成,攻击者的余额记录为0.00021594 + 0.00000001 = 0.00021595,手中已取走的0.00021594,可以看到这里攻击者已经获利,资产总和已经翻倍
第二笔攻击交易
继续看第二笔交易,交易hash为:https://cn.etherscan.com/tx/0xae7d664bdfcc54220df4f18d339005c6faf6e62c9ca79c56387bc0389274363b,
通过bloxy查看调用过程,https://bloxy.info/tx/0xae7d664bdfcc54220df4f18d339005c6faf6e62c9ca79c56387bc0389274363b
第一次调用supply函数攻击者存入0.00021593,正常执行调用过程
第二次调用supply函数攻击者存入0.00000001,此时进行重入攻击,合约执行流程进入withdraw函数,攻击者调用withdraw函数取出攻击者的余额,第一笔交易的余额0.00021595 + 第一次调用supply存入的 0.00021593 = 0.00043188,然后合约执行流程在回到supply函数,完成存入操作
最后交易持续完成,攻击者的余额记录为0.00021595 + 0.00021593 + 0.00000001 = 0.00043189,手中已取走的0.00043188,可以看到到这里,攻击者取出的金额和总资产数量都已经翻倍
第三笔攻击交易
继续看第三笔交易,交易hash为:https://cn.etherscan.com/tx/0xa0e7c8e933be65854bee69df3816a07be37e108c43bbe7c7f2c3da01b79ad95e,
通过bloxy查看调用过程,https://bloxy.info/tx/0xa0e7c8e933be65854bee69df3816a07be37e108c43bbe7c7f2c3da01b79ad95e
第一次调用supply函数攻击者存入0.00043187,正常执行调用过程
第二次调用supply函数攻击者存入0.00000001,此时进行重入攻击,合约执行流程进入withdraw函数,攻击者调用withdraw函数取出攻击者的余额,第二笔交易的余额0.00043189 + 第一次调用supply存入的 0.00043187 = 0.00086376,然后合约执行流程在回到supply函数,完成存入操作
最后交易持续完成,攻击者的余额记录为0.00043189 + 0.00043187 + 0.00000001 = 0.00086377,手中已取走的0.00086376
从上面的联系三笔交易已经可以很清楚的看到,攻击者的每一次攻击,资产总和都是翻倍的,利用滚雪球的方式不断进行攻击,盗取 imBTC 的最后一笔交易如下图:
从上图可以看到,最终在交易 https://cn.etherscan.com/tx/0xced7ca813081fb594181469001a6aff629c5874bd672cca44075d3ec768db664 中,基本已达到 imBTC 的最大存量,此时将 Lendf.Me 账户中的 imBTC 资产盗取一空。
整个攻击过程原理图如下:
漏洞代码分析
下面我们分析一下 Lendf.Me 合约代码,攻击者是如何达到上面的攻击效果的。
首先看看 Lendf.Me 合约代码,合约地址:0x0eee3e3828a45f7601d5f54bf49bb01d1a9df5ea
看一下 Lendf.Me 合约的supply函数,在supply函数的开头,申请了临时变量localResults,然后将用户的余额保存在 localResults.userSupplyCurrent中,如下图所示:
在supply函数的最后,用户存入成功后,将用户的余额进行更新,此时最后用户的余额为localResults.userSupplyUpdated,如下图所示:
在更新用户余额之前,supply调用了 doTransferIn 函数,跟进 doTransferIn 函数:
在 doTransferIn 函数中,调用了imBTC合约的transferFrom函数,imBTC合约地址:0x3212b29e33587a00fb1c83346f5dbfa69a458923,跟进imBTC合约的 transferFrom 函数:
imBTC合约的 transferFrom 函数中,接着调用 _callTokensToSend 函数,跟进:
在 _callTokensToSend 函数中,通过ERC1820 注册 了ERC777Sender
实现接口, 因此这里必须调用用户的 tokensToSend
钩子函数,这里就是攻击者重入漏洞发生的地方,攻击者在这里调用了 Lendf.Me 合约的 withdraw() 函数,此时合约执行流程从 supply 进入到了 withdraw 函数中。
在 withdraw() 函数执行完后,继续回到supply调用了 doTransferIn 函数的后面继续执行,虽然在 withdraw() 函数中,用户取出余额之后,进行了余额数量更新,但是回到supply之后,继续执行后面的内容,那么在supply的最后再次更新了用户的余额,导致覆盖了用户 withdraw 之后余额数量的值。
具体合约代码进行重入攻击的流程如下图:
通过上面的攻击过程我们可以看到,攻击者的 withdraw() 调用是发生在第二次supply调用的 transferFrom 函数中,也就是在 Lendf.Me 合约中通过 transferFrom 调用用户的 tokensToSend() 钩子函数的时,攻击者在这里通过重入的方式调用了withdraw函数,造成了重入攻击。
事件总结
DeFi由于其高收益,去中心化等优势目前比较火爆,但仍处于发展初期,还有很多机制仍需不断去完善,DeFi项目很多,但是质量都是参差不齐,而且生态中组件众多,只要有一个模块出了问题,可能就会拖垮整个项目。
零时科技安全团队针对本次攻击事件,在此建议:
遵守严格的合约安全开发规范,在进行外部调用时先修改合约变量状态再进行外部调用;
项目上线前可通过专业的第三方安全团队进行全面的安全审计,尽可能的发现潜在的安全问题;
合约中加入风控体系,比如设置暂停开关,出现不可控问题时可及时止损;
发展后续
截止发稿,借贷平台Lendf.Me的攻击者(0xa9bf70a420d364e923c74448d9d817d3f2a77822),向借贷平台Lendf.Me的admin账户(0xa6a6783828ab3e4a9db54302bc01c4ca73f17efb)转回了126,014枚PAX,320枚HBTC, 381,162枚HUSD。