深入了解重入攻擊及其有效防禦方法

重入攻擊是什麼?

重入攻擊是一種常見的智能合約安全漏洞,攻擊者通過反覆調用目標合約的函數,在目標合約完成狀態更新之前,多次提取資金或觸發惡意操作。

重入攻擊的工作原理

1. 調用階段:攻擊者通過惡意合約調用目標合約的提取函數
2. 回調階段:目標合約在更新餘額前向攻擊者轉帳
3. 再次調用:攻擊者的回退函數被執行,立即再次調用目標合約
4. 循環重複:直到目標合約資金耗盡

經典案例:The DAO 攻擊

2016年,攻擊者利用重入漏洞從The DAO合約中竊取了360萬個以太幣,導致以太坊硬分叉。

有效的防禦方法

1. 檢查-效果-交互模式(CEI)

```solidity
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool success,) = msg.sender.call{value: amount}("");
require(success);
}
```

執行順序:
- 檢查:驗證條件
- 效果:更新狀態變數
- 交互:進行外部調用

2. 互斥鎖(Mutex/Guard)

```solidity
bool locked = false;

modifier noReentrant() {
require(!locked);
locked = true;
_;
locked = false;
}

function withdraw(uint amount) public noReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool success,) = msg.sender.call{value: amount}("");
require(success);
}
```

3. 使用 OpenZeppelin 的 ReentrancyGuard

```solidity
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract SecureWithdrawal is ReentrancyGuard {
function withdraw(uint amount) public nonReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool success,) = msg.sender.call{value: amount}("");
require(success);
}
}
```

4. 拉取而非推送模式

```solidity
// 不推薦:推送模式
function sendReward(address recipient, uint amount) public {
recipient.call{value: amount}("");
}

// 推薦:拉取模式
mapping(address => uint) public rewards;

function claimReward() public {
uint amount = rewards[msg.sender];
rewards[msg.sender] = 0;
(bool success,) = msg.sender.call{value: amount}("");
require(success);
}
```

預防最佳實踐

1. 用狀態變數標記交互狀態
2. 在執行外部調用前完成所有狀態更新
3. 使用經過審計的安全庫
4. 進行全面的安全審計
5. 編寫重入測試用例
6. 使用靜態分析工具檢測

重入攻擊的變種

- 跨函數重入:攻擊不同的公共函數
- 跨合約重入:攻擊其他合約
- 讀取-修改-寫入競賽條件

結論

重入攻擊仍然是智能合約開發中最危險的威脅之一。採用檢查-效果-交互模式、互斥鎖和安全庫是防禦的關鍵。開發者應始終保持警覺,並遵循安全編碼最佳實踐。

重入攻擊是對智能合約最嚴重的安全威脅之一。本文將不僅教你重入攻擊的運作方式,還會全面介紹如何保護你的專案。

為了完整理解,我們將從基本概念開始,分析實際攻擊的源碼,最後探索三種經證明有效的防禦技術:從 modifier nonReentrant() 到 GlobalReentrancyGuard() 以及檢查-效果-互動範例。

重入攻擊是什麼類型的攻擊?

想像兩個智能合約相互交互。ContractA 和 ContractB 完全可以互相調用。這看起來很正常,但正是這個漏洞被攻擊者利用。

重入的核心概念是:一個智能合約可以在另一個合約仍在執行時,反向調用該合約。若未妥善控制,會形成無限循環。

舉例來說:ContractA 持有 10 Ether,其中 ContractB 已經向其發送了 1 Ether。正常情況下,當 ContractB 請求提款時,會先檢查(餘額 > 0),然後轉帳 Ether,最後將餘額設為 0。但在重入攻擊中,這個流程可以在更新餘額之前反覆執行多次。

重入攻擊的運作機制

攻擊者需要兩個主要部分:一個 attack() 函數和一個 fallback() 函數。

fallback() 是 Solidity 中的特殊函數——一個沒有名字、沒有參數、沒有返回值的外部函數。它會在以下情況自動觸發:

  • 呼叫不存在的函數
  • 發送資料但未指定函數
  • 轉帳 Ether 且沒有資料

以下是攻擊逐步進行的過程:

步驟 1: 攻擊者呼叫 attack(),在其中調用 ContractA 的提款函數。

步驟 2: ContractA 檢查 ContractB 的餘額是否大於 0。若是,則轉 1 Ether 給 ContractB,並觸發 fallback()。

步驟 3: 這是關鍵點——尚未更新餘額。ContractA 仍在執行提款函數。

步驟 4: fallback() 被觸發,立即反向調用 ContractA 的提款函數。

步驟 5: ContractA 再次檢查——ContractB 的餘額仍是 1 Ether(因為尚未更新)。它再次轉帳 1 Ether,並觸發 fallback()。

此過程會反覆進行,直到 ContractA 的 Ether 被全部轉出。這就是為何重入攻擊如此危險。

具體攻擊範例分析

以 EtherStore 智能合約為例,它有兩個主要函數:

  • deposit():存款並更新餘額
  • withdrawAll():一次提取全部餘額

withdrawAll() 的流程是:檢查(餘額 > 0)、轉帳 Ether、將餘額設為 0。

這裡的漏洞在於:轉帳 Ether 在更新餘額之前執行,攻擊者可以利用這點反覆調用。

攻擊合約 Attack 設計如下:

  • 一個建構子,存放 EtherStore 的地址
  • fallback() 會反覆調用 withdrawAll()
  • attack() 開始攻擊流程

攻擊流程:

  1. 攻擊者呼叫 attack(),並存入 Ether
  2. attack() 向 EtherStore 轉入 Ether,建立餘額
  3. attack() 呼叫 EtherStore 的 withdrawAll()
  4. EtherStore 轉出 Ether,觸發 fallback()
  5. fallback() 反向調用 withdrawAll()
  6. 重複此流程,直到 EtherStore 的 Ether 被全部轉出

三種防禦重入攻擊的技術

技術一:使用 Modifier nonReentrant

最簡單的保護方式是用 modifier nonReentrant。這是一個特殊的函數修飾器,可以在函數執行時鎖定合約,阻止重入。

運作方式:

  • 在函數開始時鎖定合約
  • 在函數結束時解鎖
  • 任何在鎖定期間的重入調用都會被拒絕

缺點:只能保護單一函數,無法防止多函數間的重入。

技術二:檢查-效果-互動(Check-Effect-Interaction)

這是一個更進階的設計範例,避免在轉帳前更新狀態。

流程:

  • Check(檢查):先驗證所有條件
  • Effect(效果):立即更新狀態(如餘額)
  • Interaction(互動):最後才進行外部調用(轉帳)

範例:

  • 不安全:檢查 → 轉帳 → 更新狀態
  • 安全:檢查 → 更新狀態 → 轉帳

這樣即使有重入,也因為狀態已更新,不會重複提取。

技術三:GlobalReentrancyGuard

適用於多合約交互的複雜系統。建立一個中心控制的合約,管理所有合約的重入鎖。

原理:

  • 所有合約都引用同一個全域鎖
  • 防止跨合約的鏈式重入攻擊

例如:在 ScheduledTransfer 發送 Ether 時,若 AttackTransfer 嘗試反覆調用,GlobalReentrancyGuard 會阻擋。

選擇合適的防禦技術

使用 nonReentrant:

  • 只需保護少數幾個函數
  • 成本較低
  • 不需考慮跨函數重入

使用 Check-Effect-Interaction:

  • 適合多個函數的全面保護
  • 優化 gas 費用
  • 實踐最佳安全實務

使用 GlobalReentrancyGuard:

  • 系統較複雜、多合約交互頻繁
  • 需要集中管理的安全層

最佳策略是結合使用這些技術,根據專案需求調整。

結論

重入攻擊並非不可防範,只要理解其運作原理並採用適當的技術。從簡單的 modifier 到全面的 GlobalReentrancyGuard,每個工具都在智能合約安全體系中扮演重要角色。

記住,安全不是選擇,而是必須。理解重入攻擊並實施防禦措施,能幫助你打造安全又高效的智能合約。

想了解更多 Web3 安全、Solidity、智能合約檢查等資訊,請關注 @TheBlockChainer Twitter。

查看原文
此頁面可能包含第三方內容,僅供參考(非陳述或保證),不應被視為 Gate 認可其觀點表述,也不得被視為財務或專業建議。詳見聲明
  • 讚賞
  • 留言
  • 轉發
  • 分享
留言
請輸入留言內容
請輸入留言內容
暫無留言