Naught Coin
// 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;
uint 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) override public 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 {
_;
}
}
}
Goal
Transfer the Naught Coin from your balance before the deadline expires
Exploit
For this exercise we are starting with some inital balance of Naught Coins in our account and we need to find a way how to transfer them to another account and not waiting for the deadline to expire.
If we take a look at the transfer() function inside NaughtCoin contract we notice that this function has a modifier attached to it (lockTokens) that does not allow us to transfer the tokens if the current block timestamp is not greater than the timelock. But there is something strange here, inside the modifier we check if the msg.sender is the player. The question that arises is, what if someone else can transfer our tokens on our behalf, is that possible?
In order to solve this CTF, one must be familiar with the ERC20 specification from OpenZeppelin. After some research, I found out that there is another function transferFrom inside the ERC20 specification, that can be used for transferring tokens from one to another address. This function allows us to transfer someone else's tokens on their behalf to another address. There is one thing that we should take in mind, the owner of the tokens should approve us to transfer the tokens on their behalf.
Since the Naught Coin is inheriting from the ERC20 standard, we can use the above-mentioned approach to solve this CTF
To exploit the contract we need to:
- create Attacker contract, and create instance from the NaughtCoin contract inside the Attacker contract
- call the approve function from the NaughtCoin contract (contract.approve), with the address of the Attacker contract as first parameter, and the amount we want to approve as second (in our case it will be balanceOf(msg.sender))
-
create withdrawFunds() external function, and inside this function call the transferFrom(from, to, amount) function inside the NaughtCoin contract
- our account address should be used for the from parameter
- the address of the Attacker contract should be used for the to parameter
- naughtCoin.balanceOf(msg.sender) should be used for the amount parameter
-
call the withdrawFunds() function from our account
Attacker contract code
contract Attacker {
NaughtCoin private immutable coin;
constructor(NaughtCoin _coin) {
coin = NaughtCoin(_coin);
}
function withdrawFunds() external {
coin.transferFrom(msg.sender, address(this), coin.balanceOf(msg.sender));
}
}