据慢雾区情报,以太坊 DeFi 平台 Lendf.Me 遭受重入漏洞攻击。慢雾安全团队在收到情报后随即对此次攻击事件展开分析,并快速定位了问题所在。
据慢雾科技反洗钱 (AML) 系统初步统计分析,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.08156, USDx: 510868.16067, imBTC: 291.3471
之后攻击者不断通过 1inch.exchange、ParaSwap、Tokenlon 等 DEX 平台将盗取的币兑换成 ETH 及其他代币。
以下是详细分析过程。
本次对 Lendf.Me 实施攻击的攻击者地址为 0xa9bf70a420d364e923c74448d9d817d3f2a77822
,攻击者通过部署合约 0x538359785a8d5ab1a741a0ba94f26a800759d91d
对 Lendf.Me 进行攻击。
通过在 Etherscan 上查看攻击者的其中一笔交易:https://etherscan.io/tx/0xae7d664bdfcc54220df4f18d339005c6faf6e62c9ca79c56387bc0389274363b
我们发现,攻击者首先是存入了 0.00021593 枚 imBTC,但是却从 Lendf.Me 中成功提现了 0.00043188 枚 imBTC,提现的数量几乎是存入数量的翻倍。那么攻击者是如何从短短的一笔交易中拿到翻倍的余额的呢?这需要我们深入分析交易中的每一个动作,看看究竟发生了什么。
通过把该笔交易放到 bloxy.info 上查看,我们能知道完整的交易流程
通过分析交易流程,我们不难发现攻击者对 Lendf.Me 进行了两次 supply() 函数的调用,但是这两次调用都是独立的,并不是在前一笔 supply() 函数中再次调用 supply() 函数。
紧接着,在第二次 supply() 函数的调用过程中,攻击者在他自己的合约中对 Lendf.Me 的 withdraw() 函数发起调用,最终提现
在这里,我们不难分析出,攻击者的 withdraw() 调用是发生在 transferFrom 函数中,也就是在 Lendf.Me 通过 transferFrom 调用用户的 tokensToSend() 钩子函数的时候调用的。很明显,攻击者通过 supply() 函数重入了 Lendf.Me 合约,造成了重入攻击,那么具体的攻击细节是怎样的呢?我们接下来跟进 Lendf.Me 的合约代码。
Lendf.Me 的 supply() 函数在进行了一系列的处理后,会调用一个 doTransferIn 函数,用于把用户提供的币存进合约,然后接下来会对 market 变量的一些信息进行赋值。回顾刚才说的攻击流程,攻击者是在第二次 supply() 函数中通过重入的方式调用了 withdraw() 函数提现,也就是说在第二次的 supply() 函数中,1590 行后的操作在 withdraw() 之前并不会执行,在 withdraw() 执行完之后,1590 行后的代码才会继续执行。这里的操作导致了攻击者可提现余额变多。
我们深入分析下 supply() 函数
根据上图,可以看到,在 supply() 函数的末尾,会对 market 和用户的余额进行更新,在这之前,用户的余额会在函数的开头预先获取好并保存在 localResults.userSupplyCurrent
,如下:
通过赋值给 localResults
变量的方式,用户的转入信息会先暂时保存在这个变量内,然后此时攻击者执行 withdraw() 函数,我们看下 withdraw() 函数的代码:
这里有两个关键的地方 :
1、在函数的开头,合约首先获取了 storage 的 market
及 supplyBalance
变量。
2、在 withdraw() 函数的末尾,存在同样的逻辑对 market
用户的余额信息 (supplyBalance
) 进行了更新,更新值为扣除用户的提现金额后的余额。
按正常的提现逻辑而言,在 withdraw() 单独执行的时候,用户的余额会被扣除并正常更新,但是由于攻击者将 withdraw() 嵌入在 supply() 中,在 withdraw() 函数更新了用户余额 (supplyBalance) 后,接下来在 supply() 函数要执行的代码,也就是 1590 行之后,用户的余额会再被更新一次,而用于更新的值会是先前 supply() 函数开头的保存在 localResults
中的用户原先的存款加上攻击者第一次调用 supply() 函数存款的值。
在这样的操作下,用户的余额虽然在提现后虽然已经扣除了,但是接下来的 supply() 函数的逻辑会再次将用户未扣除提现金额时的值覆盖回去,导致攻击者虽然执行了提现操作,但是余额不但没有扣除,反而导致余额增加了。通过这样的方式,攻击者能以指数级别的数量提现,直至把 Lendf.Me 提空。
针对本次攻击事件慢雾安全团队建议:
附:
OpenZeppelin ReentrancyGuard: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol
声明:本内容为作者独立观点,不代表 CoinVoice 立场,且不构成投资建议,请谨慎对待,如需报道或加入交流群,请联系微信:VOICE-V。
简介:负责报道关于比特币的一切
评论0条