> For the complete documentation index, see [llms.txt](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/ethernaut/15.-naught-coin.md).

# 15. Naught Coin

> NaughtCoin은 ERC20 토큰이며, 당신은 이미 이 모든 토큰을 보유하고 있습니다. 하지만 문제는, 10년의 잠금 기간이 지난 후에만 전송할 수 있다는 것입니다. 다른 주소로 이 토큰들을 어떻게든 옮겨서, 자유롭게 전송할 수 있는 방법을 알아낼 수 있나요? 보유한 토큰 잔액을 0으로 만들어 이 레벨을 완료하세요.
>
> 도움이 될 만한 것들:
>
> * [ERC20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md) 표준 명세
> * [OpenZeppelin](https://github.com/OpenZeppelin/zeppelin-solidity/tree/master/contracts) 코드베이스

***

### 원본 소스 코드

```solidity
// 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 {
            _;
        }
    }
}
```

***

### 공격 시나리오

* `player` 주소가 공격 컨트랙트에 `approve()` 호출
* 공격 컨트랙트에서 `transferFrom(player, 공격자, amount)` 실행
* `lockTokens`는 `msg.sender == player`일 때만 발동되므로 우회 성공
* `player`의 토큰을 모두 공격자 주소로 전송하여 잔액을 0으로 만듦

즉, 문제는 `transfer()`에만 `lockTokens` 제한이 걸려 있어, **다른 주소나 컨트랙트는 `transferFrom()`을 통해 토큰을 이동시켜 해결 할 수 있다.** `player`가 `approve()`를 통해 특정 주소(또는 컨트랙트)에 토큰 전송 권한을 주면, 이 주소는 제한 없이 `transferFrom()` 가능하다.

***

### 공격 코드

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

contract NaughtCoin {
    string public name = "NaughtCoin";
    string public symbol = "NC";
    uint8 public decimals = 18;
    uint256 public totalSupply;

    address public player;
    uint256 public timeLock = now + 10 * 365 days;

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    constructor(address _player) public {
        player = _player;
        totalSupply = 1000000 * (10 ** uint256(decimals));
        balanceOf[player] = totalSupply;
    }

    function approve(address _spender, uint256 _value) public returns (bool) {
        allowance[msg.sender][_spender] = _value;
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
        require(balanceOf[_from] >= _value, "balance too low");
        require(allowance[_from][msg.sender] >= _value, "not allowed");

        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        allowance[_from][msg.sender] -= _value;
        return true;
    }

    function transfer(address _to, uint256 _value) public returns (bool) {
        require(now > timeLock, "locked");
        return transferFrom(msg.sender, _to, _value);
    }
}
```

```solidity
//transferFrom()에는 제한이 없음
function transferFrom(address _from, address _to, uint256 _value) public returns (bool)
```

* `timeLock` 없이 정상 작동함
* `approve()`만 받으면 누구든지 대신 토큰 전송 가능하다.
* `player`가 자신에게 허용량(`approve`)을 설정해주면
* 공격자가 `transferFrom()`을 호출해 우회 하면 다음과 같이 해결할 수 있다.

<figure><img src="https://3512650551-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FvLae9iTcYKyLdHhMtRVd%2Fuploads%2FiStuWWlf7wvNr4dBhPeA%2Fimage.png?alt=media&amp;token=ddf81dec-6ae2-4ede-89d0-69d3298afe3b" alt=""><figcaption></figcaption></figure>

***

### &#x20;‼️더알아보기‼️

> 자신이 작성하지 않은 코드를 사용할 때는, 전체 구조가 어떻게 연결되어 있는지 이해하기 위해 코드를 꼼꼼히 살펴보는 것이 매우 중요합니다. 특히 여러 단계의 import가 중첩되어 있는 경우나, 특정 권한 제어(authorization control, 사람들의 작업을 허용하거나 허용하지 않을 때)를 구현하고 있을 경우에는 더욱 그렇습니다. 예를 들어 이 문제의 경우, 개발자가 코드를 흝어보며 토큰을 이동시키는 유일한 방법이 transfer 함수라고 착각할 수 있습니다. 하지만 실제로는 동일한 작업을 수행할 수 있는 다른 방식도 존재합니다.
