本文深入探讨了以太坊智能合约中一种臭名昭著且具有破坏性的漏洞——重入漏洞。文章解释了重入漏洞的原理、类型(包括单函数重入、跨函数重入和只读重入),并通过具体的合约例子进行了详细分析。此外,还提供了多种缓解重入攻击的技术手段,如在外部调用前更新状态、使用重入锁、遵循Checks-Effects-Interactions模式等。
重入是以太坊智能合约历史上最臭名昭著和最具破坏性的漏洞之一。
它利用了一个简单而强大的缺陷:在保护你自己的合约状态之前,信任外部调用。
从技术角度讲,当发生以下情况时,就会发生重入:
这使得攻击者可以重复敏感操作(如提取资金)一遍又一遍,耗尽远远超出允许范围的资产。
这种漏洞最臭名昭著的例子是 2016 年的 DAO 黑客攻击:
该事件是如此灾难性,以至于它分裂了以太坊网络:
有关攻击如何发生的详细分析,请参阅:
2.攻击者的合约可以 “回调你”
receive()
或 fallback()
函数,立即再次调用你的函数。3.没有 “一次一个” 的锁
如果你在锁定你的保险箱(更新余额)之前交出控制权(发送以太币),并且不在门口留下警卫,那么攻击者可以在你注意到之前一次又一次地走进来。
重入攻击有不同的类型,具体取决于攻击者如何以及在何处重新进入你的合约。 了解这些类型有助于你设计更好的防御措施。
经典且最常见的形式。
withdraw()
函数。fallback
或 receive()
函数会在 余额更新之前 自动再次调用同一个易受攻击的函数。示例场景:
攻击者的 fallback 在 call
期间再次调用 withdraw()
,在余额减少之前。
这是一种更微妙和高级的重入形式。
withdraw()
,然后通过不同的函数(如 transfer()
或 claimReward()
)重新进入,从而利用共享的余额状态。为什么它很棘手:
withdraw()
,它发送以太币,但在发送_之后_更新余额。transfer()
,它也会在更新之前操作相同的 balances
。在 Solidity 0.5.x 左右引入,这是一种更细微和不太明显的攻击媒介。
例子:
withdraw()
函数允许用户提取他们存入的资金。withdraw()
,提取比他们存入的更多的以太币。withdraw(1 ether)
。withdraw(1 ether)
,在余额减少之前重新进入同一个函数。图表由 Cyfrin Updraft 提供。 如需更多有价值的资源,请访问他们的网站
重入漏洞的出现是因为合约在更新用户的余额之前,通过
call
将以太币发送给调用者。 在此示例中,攻击者可以首先调用withdrawPartial
以触发一个 fallback 函数,该函数递归调用withdrawFull
,从而利用不一致的状态并耗尽资金。
withdrawPartial(1 ether)
,该函数在更新余额之前将以太币发送到攻击者的 fallback 函数。withdrawFull()
,允许资金被多次耗尽。你可以在我们的 GitHub 存储库中找到本次讨论中的所有 Solidity 代码示例,以供参考
ReentrancyGuard
来防止嵌套(递归)调用。transfer
或 send
进行以太币转账 (如果 gas 约束允许)这些方法转发有限的 gas,因此复杂的 fallback 执行更加困难(但要注意 opcode(操作码) 中的 gas 变化)。
重入漏洞可能会对智能合约造成严重损害,但只要小心谨慎并遵循最佳实践,你就可以有效地保护你的项目。 通过应用诸如检查-生效-交互之类的原则,在进行外部调用之前更新状态以及利用重入保护,你可以构建强大的防御来抵御这些攻击。
请记住,安全性是一个持续的旅程——始终彻底测试、审查你的代码,并随时了解智能合约开发中的最新技术。
🚀 准备好深入了解了吗? 此博客中的所有示例代码、测试和详细设置都可以在我的 GitHub 存储库中找到。 立即开始探索、学习和构建安全合约!
top10-smartcontract-vulnerabilities/05-Reentrancy at main ·…
- 原文链接: blog.blockmagnates.com/b...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!