SlowMist:Lendf.Me 重入攻击的详细信息

  • slowmist
  • 发布于 2020-04-21 22:48
  • 阅读 18

本文详细分析了以太坊DeFi平台Lendf.Me遭受的重入攻击,损失约2469万美元。文章介绍了攻击过程、合约的漏洞以及攻击者如何利用这些漏洞在单一交易中伪造余额获取资金。文中还提供了防御建议,包括使用重入保护机制和通过第三方审计来提高安全性。

引言

根据SlowMist Zone提供的信息,Lendf.Me,一个以太坊DeFi平台,遭受了重入攻击。SlowMist安全团队迅速分析了紧急情况,找出了问题的原因。

根据SlowMist 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)上将被盗的加密货币兑换为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() 函数后,攻击者在自己的合约中调用了 Lendf.Me 的 withdraw() 函数,最终从 Lendf.Me 提取了资金。

到此为止,我们清楚地知道, withdraw() 函数是在 transferFrom() 函数内被调用的,换句话说,它是在 Lendf.Me 调用 transferFrom 函数时被调用,这最终调用了用户合约中的 tokensTosend() 函数。显然,攻击者通过调用 supply() 函数进行了重入攻击。为了更深入地了解攻击,让我们查看 Lendf.Me 的合约代码。

代码分析

为了将用户提供的代币存入合约,Lendf.Me 的 supply() 函数在经过一系列处理后调用了名为 doTransferIn 的函数,并在这些步骤完成后,一些 Market 变量的关键值被赋值。回顾我们提到的攻击过程,在第二次调用 supply() 函数时,攻击者利用重入攻击调用了 withdraw() 函数来提取代币,这意味着,在第二次调用 supply() 的过程中,行索引大于 1590 的代码在 withdraw() 函数调用完成之前从未执行。这个操作导致攻击者提取了错误的额外余额。

让我们更详细地查看 supply() 函数:

根据这张图,市场和用户的余额会在 supply() 函数执行结束时更新。在此之前,用户的余额会在 supply() 函数执行开始时获取并保存在 localResults.userSupplyCurrent 中,如下所示:

通过给变量 localResults 赋值,用户的交易输入数据会暂时保存在这个变量中。接着,攻击者执行了 withdraw() 函数,这里是代码:

![](https://img.learnblockchain.cn/2025/03/09/1z73JB8Nnqf6XEK_-PHSnjg.jpeg)
![](https://img.learnblockchain.cn/2025/03/09/1J1mMCVxZ_WutCgluze1yFA.jpeg)

我们可以在这里找到两个关键点:

  1. 在函数开始时,合约首先获取两个存储变量,market 和 supplyBalance。
  2. withdraw() 函数的末尾,存在相同的逻辑来更新市场用户的余额信息(supplyBalance),更新后的值为扣除用户提现金额之后的余额。

对于正常和正确的提取逻辑,当 withdraw() 函数单独执行时,用户余额应该被扣除并更新为正确的值。但是由于攻击者将 withdraw() 函数嵌入到 supply() 函数中,当 supply() 后面的代码部分(行索引大于1590)执行时,用户余额再一次被更新,尽管 withdraw() 函数已经提前更新了用户余额。对于第二次更新,更新后的值将是两个部分的总和:一部分来自于 supply() 函数开始时存储在变量 localResults 中的原始用户余额值,另一部分来自于 supply() 的第一次调用中的用户余额值。

在以上所有操作完成之后,尽管用户的提现金额已从用户余额中扣除,supply() 函数后面的编程逻辑仍会再次用提现前的用户余额值覆盖用户余额,导致用户余额值的增加,而不是正常的扣除,尽管攻击者已执行了提现。通过这些方式,攻击者可以通过指数增长提取资金,直到他们清空Lendf.Me。

防御建议

SlowMist安全团队针对此次攻击的建议:

  1. 在关键业务部分引入锁机制,例如:OpenZeppelin的 ReentrancyGuard
  2. 在合约开发过程中,应采用这样的开发风格:在调用外部合约之前先更改合约的局部变量。
  3. 要求优秀的第三方安全团队进行全面的安全审计,以尽可能多地发现潜在的安全风险。
  4. 当多个合约连接时,也要检查多个合约的代码安全和业务安全,并综合考虑各种业务场景下的安全问题。
  5. 尝试设置合约暂停的开关,以便在发生黑天鹅事件时及时获悉并止损。
  6. 意识到安全是动态的,因此项目的每个部分都应捕获可能与项目相关的威胁情报,并检查潜在的安全风险。
  • 原文链接: slowmist.medium.com/slow...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
slowmist
slowmist
江湖只有他的大名,没有他的介绍。