1-line bugs lead to another $1 million stolen

Two hacks in the last two weeks (txs on BSC and Ethereum), but essentially the same bug.


CryptoBurgers on BSC had an unprotected burn function allowing anyone to trivially burn tokens from any address.

So let’s dive in a bit:

The BURG Contract is a Proxy that currently points to code where the vulnerability has been fixed. However, if we check the implementation at block 14433926, it pointed to this contract which has the exploitable implementation shown below:

function burn(address _account, uint256 _amount) external returns (bool) {
    _burn(_account, _amount);
    return true;

function _burn(address account, uint256 amount) internal virtual {
    require(account != address(0), "ERC20: burn from the zero address");

    _beforeTokenTransfer(account, address(0), amount);

    uint256 accountBalance = _balances[account];
    require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
    unchecked {
        _balances[account] = accountBalance - amount;
    _totalSupply -= amount;

    emit Transfer(account, address(0), amount);

    _afterTokenTransfer(account, address(0), amount);

So no check that the caller has approval to burn tokens belonging to _account.

The hacker takes a flash loan of 50 BNB from the WBNB/CAKE pair and uses is to buy BURG. Then the hacker drains in batches of 20,000 tokens at a time, from the BURG/WBNB pair, to stay under the the max single-transfer amount instituted by _beforeTokenTransfer. After the pool has been drained of almost all BURG tokens, the price of BURG, of course, skyrockets relative to WBNB.

The hacker then swaps the 50 BNB worth of BURG purchased at the beginning of the transaction to almost 1000 BNB. The 50 BNB flash loan is repaid and the hacker walks off with almost 950 BNB.

Solution: burning should check allowances.

Ethereum (TCR)

Slightly more subtle but same general concept, TecraCoin (TCR) which launched on Ethereum had this burnFrom:

function burnFrom(address from, uint256 amount) external {
    require(_allowances[msg.sender][from] >= amount, ERROR_ATL);
    require(_balances[from] >= amount, ERROR_BTL);
    _approve(msg.sender, from, _allowances[msg.sender][from] - amount);
    _burn(from, amount);

So this one checks allowances but does so incorrectly. allowances[msg.sender][from] should be allowances[from][msg.sender]

The hacker exploits this bug by approving the USDT/TCR pair allowing him to drain from the pair. Same idea as with BURG, the pair is all but drained of TCR and then a trival amount of initially purchasd TCR is used to extract all of the USDT from the pair).