🛡️ 智能合约安全专题(一):什么是重入攻击?------从 DAO 事件谈起
"DAO 攻击的本质,是一个简单但致命的逻辑顺序错误。"
本文将带你看懂什么是重入攻击,它是如何发生的,又该如何避免它。
💣 一、重入攻击是什么?
重入攻击(Reentrancy Attack) 是一种利用智能合约执行过程中"外部调用未完成就重复进入当前函数"的漏洞,攻击者可以在未更新状态前多次调用目标函数,从而重复提取资金或资源。
通俗理解:
本来你打算先扣钱再发货 ,结果却先发货,钱还没扣,用户多次点击"下单"就拿走了很多件货。
🧪 二、经典示例:The DAO 如何被攻击?
在 DAO 的合约中,存在如下逻辑:
function withdraw() public {
require(balances[msg.sender] > 0);
// 1. 转账给用户(外部调用)
msg.sender.call.value(balances[msg.sender])();
// 2. 然后将余额置为0
balances[msg.sender] = 0;
}
问题是:
-
Solidity 的
call.value()
会执行msg.sender
的 fallback 函数(即攻击者的合约函数) -
攻击者的 fallback 函数中再次调用
withdraw()
-
因为还没执行
balances[msg.sender] = 0
,所以余额还在,可以再次转账 -
如此循环,直到合约资金被耗尽
攻击者合约伪代码如下:
function fallback() external payable {
if (address(target).balance > 0) {
target.withdraw();
}
}
🧠 三、重入攻击的条件
重入攻击并不是"万能钥匙",它需要满足以下条件:
条件 | 描述 |
---|---|
🔁 外部调用 | 目标合约必须对外部合约或地址发起调用(比如转账) |
⏳ 状态延迟更新 | 目标合约在调用外部合约之前没有先更新状态 |
🔁 可重入函数 | 目标函数没有限制"再次调用"或"互斥锁" |
💰 有利益可盗 | 被攻击函数能转出资金或重要资源 |
🛡️ 四、如何防止重入攻击?
✅ 1. 检查-效果-交互(CEI 模式)
推荐做法:
-
检查:验证参数、条件
-
效果:更新合约内部状态
-
交互:最后才调用外部合约或发送资金
function withdraw() public {
uint amount = balances[msg.sender];
require(amount > 0);balances[msg.sender] = 0; // 状态先更新! (bool success, ) = msg.sender.call{value: amount}(""); require(success, "Transfer failed.");
}
✅ 2. 使用 reentrancyGuard
OpenZeppelin 提供了一个标准的 ReentrancyGuard 工具,使用 nonReentrant
修饰函数,阻止嵌套调用:
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Bank is ReentrancyGuard {
function withdraw() public nonReentrant {
// 安全的提取逻辑
}
}
✅ 3. 使用 transfer()
或 send()
限制 Gas
虽然不推荐(因 EIP-1884 改变 Gas 限制),但早期使用 .transfer()
的 2300 gas 限制可阻止复杂合约执行 fallback 函数。
🧯 五、重入攻击之后:Solidity 与社区的演化
DAO 攻击后,Solidity 与社区做了多项改进:
改进 | 描述 |
---|---|
✅ ReentrancyGuard |
成为智能合约防御标准配置 |
🔧 开发者工具 | 各种静态分析工具支持检测重入风险(如 Slither、MythX) |
🧪 安全审计 | 合约上线前必须通过专业审计 |
📝 编码规范 | CEI 模式成为默认模式 |
🧩 六、真实案例简析
除了 DAO,还有多个知名项目因重入攻击损失惨重:
-
SpankChain(2018):因未更新状态即转账,被攻击者提走 40000 美元
-
dForce/Lendf.me(2020):DeFi 项目遭重入漏洞被盗走近 2500 万美元
🧠 七、小结
问题 | 回答 |
---|---|
💥 重入攻击是如何发生的? | 合约外部调用时被恶意"递归调用",因为状态尚未更新 |
🔐 如何防御? | 按照 CEI 模式设计、使用 nonReentrant 、避免复杂 fallback |
🚨 教训是什么? | 智能合约不可修改,必须在上线前 100% 审核与测试 |