重入攻击(Reentrancy)是智能合约领域历史最悠久、危害最大的漏洞之一。本进阶教程将从原理到防御,系统拆解这一攻击的全貌,适合已掌握 Solidity基础进阶教程 的开发者深入研读。
什么是重入攻击
重入攻击的核心在于:当合约 A 调用外部合约 B(或向某地址转账)时,控制权会临时交给 B。若 A 在更新自身状态之前就发起了这次外部调用,B 就能在回调中再次调用 A 的同一函数,反复提取资金。著名的 The DAO 事件正是源于此类缺陷。理解这一点是 Solidity安全进阶教程 的起点。
攻击机制原理
以一个存在缺陷的提款函数为例:合约先用 call 向用户转账,再把用户余额置零。问题在于转账触发了攻击者合约的 receive 或 fallback 函数,而此时余额尚未清零,攻击者便可在回调里再次调用提款,形成循环。
这背后涉及 EVM 的执行模型,深入理解需要配合 EVM进阶教程 与 以太坊节点进阶教程 中关于调用栈与 gas 转发的内容。值得一提的是,transfer 与 send 仅转发 2300 gas 的旧式防护,在 EIP-1884 之后已不再可靠,因此不能把它当作万能盾牌。关于成本细节可参考 进阶教程Gas费。
复现步骤
在本地环境复现重入攻击有助于建立直觉。推荐流程如下:
- 编写一个含缺陷的金库合约,部署到测试链。
- 编写攻击者合约,在其
fallback中递归调用金库提款。 - 用 Foundry进阶教程 或 Hardhat部署安全审计 搭建测试用例,断言攻击前后余额变化。
- 通过 Remix IDE怎么用 单步调试,观察调用栈的重入时机。
完整可运行的范例可对照 Solidity进阶代码示例,逐行理解状态变更与外部调用的先后顺序。
防御方案
业界公认的防御手段有三类,建议组合使用:
检查—生效—交互模式(CEI)
先做条件检查,再更新状态变量,最后才进行外部调用。这是成本最低、最根本的做法,应作为默认编码习惯。
重入锁
使用互斥锁(mutex)防止函数被重复进入。OpenZeppelin使用进阶教程 提供的 ReentrancyGuard 修饰器是经过广泛审计的标准实现,可直接复用。但需注意它只防同一合约内的重入,跨合约只读重入仍需额外考量。
拉取式支付
将"主动推送资金"改为"用户自行提取",把外部调用从敏感逻辑中剥离,可大幅降低攻击面。
更全面的攻击面分析,建议结合 Solidity进阶安全审计 的检查清单逐项核对。
优势与风险提示
掌握重入防御能显著提升合约安全性,但请务必清醒认识到:没有任何单一手段可保证绝对安全。只读重入、跨函数重入、以及与 Sandwich攻击进阶教程、抢跑交易进阶教程 等 MEV 手法的组合,都可能绕过常规防护。涉及真实资金的合约,上线前应经过专业第三方审计,并通过形式化验证补强。本文不构成任何安全担保,亦不构成投资建议,链上操作存在不可逆的资金损失风险。
常见问题
问:用了 ReentrancyGuard 是不是就高枕无忧? 答:不是。它只防护单合约同函数重入,跨合约只读重入、逻辑漏洞仍需独立审查。
问:升级代理合约会引入重入风险吗? 答:可能。代理与实现分离时,存储布局与初始化顺序都可能成为隐患,详见 合约升级模式进阶教程。
问:审计能 100% 排除重入吗? 答:审计能大幅降低风险但无法穷尽所有路径,应配合监控与漏洞赏金长期防护。