如何认识与预防区块链智能合约中的重入攻击漏洞

·

在公链环境中,所有智能合约的代码和信息都是公开透明的,任何人都可以调用或分析。这种开放性虽然有利于去中心化应用的运行,但也带来了潜在的安全风险。重入攻击(Re-Entrance Attack)是以太坊等公链中常见的一种攻击方式,攻击者通过递归调用合约函数,能够盗取合约中的全部资金。本文将深入分析重入攻击的原理,并提供实用的防范方案。

什么是重入攻击?

重入攻击是指攻击者在合约执行过程中,通过递归调用当前函数或相关函数,使得合约在执行转账等关键操作前重复进入某些逻辑,从而绕过原有的检查机制。

例如,当合约A调用合约B时,合约B又能够在合约A尚未完成当前操作的情况下,回调合约A中的函数。这种“回调—再进入”的机制如果未被合理控制,就可能导致资金被多次提取或状态被错误更新。

值得注意的是,重入攻击不仅存在于以太坊,也出现在其他支持智能合约的公链中。

重入攻击的发生条件

要发起一次重入攻击,通常需要以下几个条件:

一个典型的重入攻击示例

以下是一段存在重入漏洞的资金管理合约代码:

contract VulnerableBank {
    mapping(address => uint) public balances;
    
    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }
    
    function withdraw() public {
        uint amount = balances[msg.sender];
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
        balances[msg.sender] = 0;
    }
}

该合约的 withdraw 函数先执行转账操作,再更新用户余额。攻击者可以利用这一点,在接收以太币的 receive 函数中再次调用 withdraw,从而形成递归转账,直到合约资金被全部提取。

如何防范重入攻击?

方案一:使用安全的转账函数

Solidity 中提供的 transfersend 函数在转账时仅会传递 2300 Gas,这个数量不足以支持被调用合约执行复杂逻辑(包括重入调用)。而低级函数 call 则允许传递全部 Gas,因此风险较高。建议在非必要情况下避免使用 call 进行转账。

方案二:遵循“检查-生效-交互”模式

这是一种良好的开发实践,即:

以前文漏洞合约为例,修复后的 withdraw 函数应改为:

function withdraw() public {
    uint amount = balances[msg.sender];
    balances[msg.sender] = 0;
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}

这样即使发生重入,余额已被清零,无法再次提取。

方案三:使用重入防护合约

OpenZeppelin 合约库提供了 ReentrancyGuard 合约,通过修饰器机制防止函数被重入。使用时只需让目标合约继承 ReentrancyGuard,并在易受攻击的函数上添加 nonReentrant 修饰器即可:

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

contract SecureBank is ReentrancyGuard {
    function withdraw() public nonReentrant {
        // 安全逻辑
    }
}

这种方法简单有效,适用于大多数场景。

常见问题

重入攻击只在以太坊上发生吗?

不是。任何支持智能合约且允许合约间调用的区块链都可能存在重入风险,如 BSC、FISCO BCOS 等。开发者应普遍重视该问题。

是否所有转账操作都必须用 transfer 或 send?

不一定。在某些复杂场景下(如需要更多 Gas 完成接收逻辑),仍可能需要使用 call 方法,但必须配合状态先更新、或使用重入防护机制。

如何测试合约是否具有重入漏洞?

可采用单元测试模拟攻击流程,或使用静态分析工具(如 Slither、MythX)对合约代码进行扫描。也可在 Remix 等开发环境中部署攻击合约进行验证。

👉 获取更多智能合约安全实践

总结

重入攻击是智能合约开发中的经典安全问题,其根源在于状态更新与外部调用之间的顺序不当。通过采用安全的转账函数、遵循“检查-生效-交互”模式、或直接使用重入防护合约,可以显著降低相关风险。开发者应在合约上线前进行充分测试与代码审计,保障资产安全。