智能合約中的重入攻擊:理解這種脆弱性並實施預防策略

重入攻擊代表了智能合約安全中最臭名昭著的漏洞之一,導致數百萬的資金在各種區塊鏈協議中被盜。本文探討了重入漏洞背後的機制,並提出了安全意識強的開發者應該實施的全面預防技術。

什麼是重入攻擊?

在其核心,重入攻擊發生在智能合約中的一個函數在其之前的執行完成之前被重復調用。當智能合約在解決自己的狀態變化之前調用外部合約時,就會產生根本的脆弱性,從而創造出被利用的機會。

在典型場景中,合約A通過調用合約B的某個函數進行交互。當合約B獲得在合約A仍在執行其原始函數時回調合約A的能力時,關鍵的安全漏洞就出現了。這種遞歸交互模式爲潛在的攻擊創建了基礎。

重入攻擊是如何工作的:逐步解析

考慮這種情況:合約A總共持有10個ETH,其中合約B已向合約A存入1個ETH。當合約B試圖通過以下序列提取其資金時,漏洞變得可被利用:

  1. 合約B調用合約A中的withdraw()函數
  2. 合約A驗證合約B的餘額大於0 (通過檢查)
  3. 合約 A 將 ETH 發送到合約 B,觸發合約 B 的回退函數
  4. 在合同A能夠將合同B的餘額更新爲零之前,合同B中的回退函數再次調用withdraw()
  5. 合約 A 檢查合約 B 的餘額,仍然顯示 1 ETH (尚未更新)
  6. 該過程重復,直到合同A的資金被耗盡

關鍵漏洞在於 餘額更新發生在 ETH 轉移之後,這使得攻擊者可以在餘額被設置爲零之前多次利用相同的餘額。

攻擊的解剖:技術實現

讓我們來檢查一下脆弱的代碼模式:

固態性 // 易受攻擊的 EtherStore 合約 合約 EtherStore { mapping(address => uint) 公共餘額;

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

函數 withdrawAll() public { uint bal = 餘額[msg.sender]; require(bal > 0);

    // 漏洞:在狀態更新之前進行外部調用

(bool發送,) = msg.sender.call{value: bal}(“”); require(sent, "發送以太幣失敗");

    // 狀態更新發生得太晚

餘額[msg.sender] = 0; } }

現在,讓我們看看攻擊者如何利用這個漏洞:

固態性 // 利用重入漏洞的攻擊合約 合約攻擊 { EtherStore 公有 etherStore;

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

// 備降函數,當 EtherStore 發送以太幣時被調用
receive() 外部可支付 {
    如果(地址(以太坊存儲).餘額 >= 1 以太坊) {
        // 重新進入 withdrawAll 函數

etherStore.withdrawAll019283746574839201(; } }

函數 attack)( external payable { 需求019283746574839201msg.value >= 1 ether);

    // 存款以在 EtherStore 中建立餘額

etherStore.deposit{value: 1 ether}();

    // 開始提款流程,觸發重入攻擊

etherStore.withdrawAll019283746574839201(; } }

攻擊序列在攻擊者調用 attack)( 時開始。這將存入 1 ETH 以建立餘額,然後調用 withdrawAll)(。當 EtherStore 發送 ETH 回來時,它會觸發 Attack 合約中的 receive)( 函數,該函數在餘額更新之前再次調用 withdrawAll)(。這一循環持續進行,直到 EtherStore 的資金被抽空。

全面預防技術

注重安全的開發者可以實施三種強大的技術來防止重入漏洞:

) 1. 功能級保護:非重入修飾符

在個別功能級別上最常見的保護措施是實現重入保護:

智能合約語言 合約 ReentrancyGuard { bool private locked = false;

修飾符 nonReentrant() { require###!locked, “可重入調用”(; 鎖定 = true; _; 鎖定 = false; }

// 將此修飾符應用於易受攻擊的函數

function withdrawFunds)( public nonReentrant { // 函數代碼防止重入 } }

這種方法有效地鎖定合約在函數執行期間,防止任何遞歸調用,直到函數完成並解鎖狀態。

) 2. 跨功能保護:檢查-效果-互動模式

這個基礎安全模式重構代碼,以消除多個函數中的漏洞:

堅固 // 易受攻擊的實現 函數 withdrawAll() public { uint bal = 餘額[msg.sender]; require###bal > 0(;

)bool發送,( = msg.sender.call{value: bal})“”(; require)sent, "發送以太幣失敗"(;

餘額[msg.sender] = 0;交互後的狀態更新 }

// 安全實施 函數 withdrawAll)( public { uint bal = 餘額[msg.sender]; require)bal > 0(;檢查

餘額[msg.sender] = 0;效果 )state changes(

)bool發送,( = msg.sender.call{value: bal})“”(;相互 作用 require)sent, "發送以太幣失敗"(; }

通過遵循 檢查-效果-交互 模式,合約在任何外部交互之前更新其狀態,確保即使攻擊者試圖重入,他們也會遇到更新後的狀態 )零餘額(。

) 3. 項目級保護:全球重入保護

對於具有多個交互合同的復雜項目,實現全局重入保護可以提供系統範圍的保護:

固態性 合約 GlobalReentrancyGuard { bool 私有 _notEntered = true;

修飾符 globalNonReentrant() { require###_notEntered, “ReentrancyGuard: reentrant call”(; _notEntered = false; _; _notEntered = 真; } }

// 項目中的所有合約都繼承自 GlobalReentrancyGuard 合約 SecureContract 是 GlobalReentrancyGuard { function vulnerableOperation)( public globalNonReentrant { // 保護整個項目免受重入攻擊 } }

這種方法在保護DeFi協議中的跨合約重入攻擊方面尤其有價值,因爲多個合約相互之間進行交互。

重入漏洞的現實影響

重入漏洞導致了區塊鏈歷史上最具破壞性的攻擊之一。臭名昭著的2016年DAO黑客事件導致約360萬ETH被盜,當時價值約)百萬,最終導致以太坊硬分叉,創造了以太坊經典。

最近,在2020年,Lendf.Me協議因重入攻擊損失了大約(百萬,突顯出盡管人們的意識有所提高,這些漏洞仍然對智能合約安全構成重大風險。

安全最佳實踐

除了提到的具體技術,開發者還應遵循以下額外的安全實踐:

  1. 始終使用最新的Solidity版本,以提供改進的安全性特性
  2. 主題合同需經過聲譽良好的公司進行全面安全審計
  3. 實施全面的測試覆蓋,包括對邊界情況的單元測試
  4. 在將新合約部署到生產環境時,先從最小的ETH暴露開始
  5. 考慮對高價值合同進行形式驗證

通過實施這些防御技術和遵循安全最佳實踐,開發者可以顯著降低重入攻擊的風險,並爲區塊鏈應用構建更安全的智能合約。

ETH0.24%
ETC-0.83%
查看原文
此頁面可能包含第三方內容,僅供參考(非陳述或保證),不應被視為 Gate 認可其觀點表述,也不得被視為財務或專業建議。詳見聲明
  • 讚賞
  • 留言
  • 轉發
  • 分享
留言
0/400
暫無留言
交易,隨時隨地
qrCode
掃碼下載 Gate App
社群列表
繁體中文
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)