Ethernaut Lvl 15 Naught Coin Walkthrough: How to abuse ERC20 tokens and bad ICOs

This is a in-depth series around Zeppelin team’s smart contract security puzzles. We learn key Solidity concepts to solve the puzzles 100% on your own.

This levels requires you to abuse a bad ERC20 implementation and control the NaughtCoin token.


What is ERC20

ERCs (Ethereum Request for Comment) are protocols that allow you to create tokens on the blockchain. ERC20, specifically, is a contract interface that defines standard ownership and transaction rules around tokens.

From: zeppelin-solidity/contracts/token/ERC20/StandardToken.sol

Contextually, ERC20 was cool in 2015 because it was like an API that all developers agreed on. For the first time, anyone could create a new asset class. Developers came up with tokens like Dogecoin, Kucoin, Dentacoin… and could trust that their tokens were accepted by wallets, exchanges, and contracts everywhere.

Assortment of ERC20 coins that ICO’ed

ERC20 single-handedly enabled the ICO craze of 2017… as well as the many ICO security issues that followed.

Security issues that accompanied ERC20

  • Batchoverflow: because ERC20 did not enforce SafeMath, it was possible to underflow integers. As we learned in lvl 5, this meant that depleting your tokens under 0 would give you 2^256 - 1 tokens!
  • Transfer “bug”: makers of ERC20 intended for developers to use approve() & transferfrom() function combination to move tokens around. But this was never clearly stated in documentation, nor did they warn against using transfer() (which was also available). Many developers used transfer() instead, which locked many tokens forever.
As we learned in lvl 9, you can’t guarantee 3rd contracts will receive your transfer. If you transfer tokens into non-receiving parties, you will lose tokens forever, since the token contract already decremented your own account’s balance.
  • Poor ERC20 inheritance: some token contracts did not properly implement the ERC interface, which led to many issues. For example, Golem’s GNT didn’t even implement the crucial approve() function, leaving transfer() as the only, problematic option.
*hint* likewise, this level didn’t implement some key functions — leaving Naughtcoin vulnerable to attack.

Detailed Walkthrough

In level 11, we learned to not blindly trust other contracts that inherit from interfaces. In short, you cannot ascertain whether these contracts have i) properly implemented a function as intended and ii) adhered to function modifier promises like pure and view.

In this level, we learn that poorly implemented, non-adversarial contracts can also be abused.

  1. In Remix, access your contract instance.
Note: Remix will not be able to find the file path, look up the StandardToken.sol file here for a working actual path, or paste the file into Remix.

Notice the following:

  • You can use both transfer() and transferFrom() to move tokens around
  • The lockTokens() modifier is only applied to the transfer() function
  • Approve() and transferFrom() functions are also available in Remix IDE

This means you can move tokens around, bypassing lockTokens, using the advised approve+transferfrom combination.

2. Check your account balance, then invoke approve() with your own address and the exact account balance.

3. Invoke transferFrom() with the parameters: i) your player address, ii) an arbitrary external wallet, and iii) the account balance.

4. Submit the instance back into the level to pass.


Key Security Takeaways

  • When interfacing with contracts or implementing an ERC interface, implement all available functions.
  • If you plan to create your own tokens, consider newer protocols like: ERC223, ERC721 (used by Cryptokitties), ERC827 (ERC 20 killer).
  • If you can, check for EIP 165 compliance, which confirms which interface an external contract is implementing. Conversely, if you are the one issuing tokens, remember to be EIP-165 compliant.
  • Remember to use SafeMath to prevent token under/overflows (as we learned in lvl 5)

More Levels