福利加码,Gate 廣場明星帶單交易員三期招募開啟!
入駐發帖 · 瓜分 $30,000 月度獎池 & 千萬級流量扶持!
如何參與:
1️⃣ 報名成為跟單交易員:https://www.gate.com/copytrading/lead-trader-registration/futures
2️⃣ 報名活動:https://www.gate.com/questionnaire/7355
3️⃣ 入駐 Gate 廣場,持續發布交易相關原創內容
豐厚獎勵等你拿:
首發優質內容即得 $30 跟單體驗金
每雙周瓜分 $10,000U 內容獎池
Top 10 交易員額外瓜分 $20,000U 登榜獎池
精選帖推流、首頁推薦、周度明星交易員曝光
詳情:https://www.gate.com/announcements/article/50291
深入了解重入攻擊及其有效防禦方法
重入攻擊是什麼?
重入攻擊是一種常見的智能合約安全漏洞,攻擊者通過反覆調用目標合約的函數,在目標合約完成狀態更新之前,多次提取資金或觸發惡意操作。
重入攻擊的工作原理
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 中的特殊函數——一個沒有名字、沒有參數、沒有返回值的外部函數。它會在以下情況自動觸發:
以下是攻擊逐步進行的過程:
步驟 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 智能合約為例,它有兩個主要函數:
withdrawAll() 的流程是:檢查(餘額 > 0)、轉帳 Ether、將餘額設為 0。
這裡的漏洞在於:轉帳 Ether 在更新餘額之前執行,攻擊者可以利用這點反覆調用。
攻擊合約 Attack 設計如下:
攻擊流程:
三種防禦重入攻擊的技術
技術一:使用 Modifier nonReentrant
最簡單的保護方式是用 modifier nonReentrant。這是一個特殊的函數修飾器,可以在函數執行時鎖定合約,阻止重入。
運作方式:
缺點:只能保護單一函數,無法防止多函數間的重入。
技術二:檢查-效果-互動(Check-Effect-Interaction)
這是一個更進階的設計範例,避免在轉帳前更新狀態。
流程:
範例:
這樣即使有重入,也因為狀態已更新,不會重複提取。
技術三:GlobalReentrancyGuard
適用於多合約交互的複雜系統。建立一個中心控制的合約,管理所有合約的重入鎖。
原理:
例如:在 ScheduledTransfer 發送 Ether 時,若 AttackTransfer 嘗試反覆調用,GlobalReentrancyGuard 會阻擋。
選擇合適的防禦技術
使用 nonReentrant:
使用 Check-Effect-Interaction:
使用 GlobalReentrancyGuard:
最佳策略是結合使用這些技術,根據專案需求調整。
結論
重入攻擊並非不可防範,只要理解其運作原理並採用適當的技術。從簡單的 modifier 到全面的 GlobalReentrancyGuard,每個工具都在智能合約安全體系中扮演重要角色。
記住,安全不是選擇,而是必須。理解重入攻擊並實施防禦措施,能幫助你打造安全又高效的智能合約。
想了解更多 Web3 安全、Solidity、智能合約檢查等資訊,請關注 @TheBlockChainer Twitter。