我只是想分享一個許多開發者常忽略的智能合約安全問題——重入攻擊。如果你正在用 Solidity 開發智能合約,這是你絕對要了解清楚的。



最簡單的說法,重入攻擊發生在一個合約調用另一個合約時,而被調用的合約可以在仍在執行中時再次調用原本的合約。想像你有 ContractA,裡面存有 10 Ether,ContractB 向它發送 1 Ether。當 ContractB 提取資金時,ContractA 會檢查餘額是否大於 0,如果是,就會發送 Ether 回去。然而,如果 ContractB 有一個 fallback function((備用函數)),它可以在 ContractA 的提取函數尚未完成時再次調用 ContractA 的提取函數。結果是?ContractB 的餘額仍被記錄為 1 Ether,因此它會再獲得 1 Ether,然後重複這個過程,直到 ContractA 的資金耗盡。

這種攻擊方式是怎麼運作的?攻擊者需要兩個東西:一個 attack( 函數來啟動攻擊,以及 fallback function 來再次調用提取函數。fallback function 是一個特殊的外部函數,沒有名字、沒有參數,任何人都可以通過調用不存在的函數、傳送沒有資料的交易,或直接發送 Ether(不帶資料)來觸發它。

舉個具體的例子:EtherStore 合約有一個 deposit) 函數用來存款,以及一個 withdrawAll( 函數用來提取全部資金。問題在於 withdrawAll) 會先檢查餘額、發送 Ether,然後才將餘額設為 0。這就留下了重入攻擊的空隙。

那麼,怎麼防範呢?我會介紹三個方法。

第一,使用 noReentrant 修飾符。這個想法很簡單:在函數執行時鎖住合約。如果有人試圖再次調用該函數,會先通過鎖的檢查,但鎖只會在函數完全執行完畢後才解開。修飾符是一種特殊的函數,可以讓你在不重複寫整個邏輯的情況下,為其他函數加入條件。

第二,採用 Check-Effect-Interaction 模式。不要在檢查條件、轉帳後再更新狀態,而是先檢查條件,立即更新狀態(例如餘額設為 0),再進行外部交互。這樣即使發生重入,餘額已經更新為 0,攻擊者就無法再提取額外資金。

第三,如果你的專案涉及多個合約相互作用,則需要用到 GlobalReentrancyGuard。不是只鎖定單一函數,而是用一個存放在獨立合約中的狀態變數,來鎖定整個系統。每次任何合約的任何函數被調用時,都會檢查系統是否被鎖定。如果已鎖定,交易就會被拒絕。這在你有像 ScheduledTransfer 這樣的合約,向 AttackTransfer 發送資金時特別有用——GlobalReentrancyGuard 可以阻止整個重入攻擊鏈的發生。

這三種方法的優點是可以根據情況搭配使用。一個重要的函數就用 noReentrant;多個相關函數用 Check-Effect-Interaction;整個專案較為複雜則用 GlobalReentrancyGuard。理解重入攻擊及其防範措施,能幫助你打造更安全的智能合約。
查看原文
此頁面可能包含第三方內容,僅供參考(非陳述或保證),不應被視為 Gate 認可其觀點表述,也不得被視為財務或專業建議。詳見聲明
  • 打賞
  • 留言
  • 轉發
  • 分享
留言
請輸入留言內容
請輸入留言內容
暫無留言