什麼是 Solidity 的重入攻擊?
作者 Alchemy
**重入攻擊(reentrancy attack)**在Solidity中會反覆從智能合約中提領資金,並將其轉移至未經授權的合約,直到資金耗盡為止。這種攻擊發生在區塊鏈的執行週期中,也就是惡意人士找到可被利用的智能合約的時候。重入攻擊已從DAO和區塊鏈協議中掠奪了數百萬美元。
以下文章將說明重入攻擊的運作機制、兩種重入攻擊的類型,以及Solidity 開發者可採取哪些預防措施,以保護 Ethereum 和 Solana 區塊鏈上的智能合約免受漏洞攻擊。
什麼是重入攻擊?
重入攻擊發生在智能合約函式暫時放棄交易的控制流程,向可能由未知或具敵意的行為者所編寫的合約發出外部呼叫時。這使得後者的合約能夠對主要的智能合約函式進行遞迴呼叫,藉此掏空其資金。
Ethereum 區塊鏈上智能合約的執行週期會先檢查餘額、發送資金,然後才更新餘額。在智能合約處於此暫存狀態時,惡意人士可以發起另一次呼叫來提領資金。這個週期會反覆進行,直到所有資金實際上都被掏空為止。
重入攻擊是如何運作的?
**重入攻擊會建立一個遞迴流程,在兩個智能合約之間轉移資金:一個是有漏洞的合約,另一個是惡意合約。**以下是重入攻擊的步驟:
- 惡意人士對有漏洞的合約「X」發出呼叫,要求將資金轉移至惡意合約「Y」。
- 合約 X 判斷攻擊者是否擁有所需資金,接著開始將資金轉移至合約 Y。
- 合約 Y 收到資金後,會執行一個「回呼」(callback)函式,在餘額更新之前再次呼叫合約 X。
- 這個遞迴流程會持續進行,直到所有資金都被耗盡並轉移完畢。
下圖說明了此攻擊情境:

重入攻擊有哪些不同類型?
重入攻擊有兩種類型:單一函式重入攻擊與跨函式重入攻擊。
1. 單一函式重入攻擊
單一函式重入攻擊發生在有漏洞的函式與攻擊者試圖遞迴呼叫的函式相同時。相較於跨函式重入攻擊,單一函式重入攻擊較為單純,也較容易預防。
2. 跨函式攻擊
跨函式重入攻擊只有在有漏洞的函式與另一個對攻擊者有利的函式共享狀態時才可行。跨函式攻擊較難偵測,也更難預防。
3. 跨合約攻擊
跨合約重入攻擊發生在一個合約的狀態在完全更新之前,被另一個合約呼叫時。跨合約重入攻擊通常發生在多個合約手動共享同一個變數,且其中某些合約以不安全的方式更新該共享變數時。
Solidity 重入攻擊案例
以下幾起著名的重入攻擊事件,進一步說明惡意人士如何利用區塊鏈協議中的漏洞:The DAO 事件、Lendf.me,以及Cream Finance。
1. DAO 事件(2016 年)
Ethereum 的 DAO 遭駭,損失約 6,000 萬美元的 Ether。Ethereum 的 DAO 原本被設計成一個投資基金,讓網路成員能夠直接對投資決策進行投票。
DAO 募集了約 1.5 億美元,但專家與社群參與者對於儲存資金的智能合約安全性表示疑慮。由於原始碼中存在遞迴呼叫的錯誤,這些鎖定在智能合約中的資金容易遭受重入攻擊。在開發團隊修復問題之前,駭客便發動攻擊並掏空了合約。
2. Lendf.me 協議事件(2020 年)
2020 年 4 月,一名惡意人士利用重入攻擊,從 Lendf.me 協議(一個在 Ethereum 網路上提供借貸業務的去中心化金融協議)竊取了 2,500 萬美元。
該協議的開發者忽略了一個事實:ERC-777 代幣包含一個回呼函式,會在資金發送或接收時通知使用者。駭客利用了這項漏洞,將惡意智能合約設為接收方,掏空了 Lendf.me 協議 99.5% 的資金。
3. Cream Finance 事件(2021 年)
2021 年 10 月,一名惡意人士利用該協議「閃電貸」(flash loan)功能上的重入攻擊,竊取了超過 1.3 億美元的 ERC-20 代幣與 CREAM 流動性協議(LP)代幣。此次攻擊的根本原因,是 AMP 錯誤地整合進 CREAM Finance 協議之中。
如何預防重入攻擊
**建立嚴謹的區塊鏈安全框架,對於預防及減輕重入攻擊可能造成的損害至關重要。**以下防範重入攻擊的最佳實務,將能協助開發者及更廣大的 web3 社群保護其資金:檢查、效果與互動(CEI)、重入防護(reentrancy guards)、提取式付款(pull payments),以及 gas 上限。
檢查、效果與互動(CEI)
CEI 流程是一種預防重入攻擊的基本方法。檢查(checks)指的是條件的真實性,效果(effects)指的是因互動而產生的狀態變更,互動(interactions)則是指函式或合約之間的交易。
在互動之前執行狀態變更所伴隨的潛在安全風險與漏洞,是開發者需要特別考量的重點。
重入防護鎖或互斥鎖(Reentrancy guard or mutex)
重入防護鎖或互斥鎖可以建立為一個函式或函式修飾詞。在容易受到重入攻擊的函式呼叫周圍,設置一個布林鎖。這表示「locked」的初始狀態為 false,但在容易受攻擊的函式開始執行之前,會立即將其設為 true,並在函式執行結束後迅速改回 false。
提取式付款(Pull payment)
採用提取式付款方法能建立更安全的端到端交易。提取式付款流程要求使用中介託管來發送資金,以避免與可能具敵意的合約直接接觸。
透過中介託管發送資金,可保護智能合約的資金免受重入攻擊。但如果該託管機制同時管理多個帳戶的資金,本身仍可能遭受重入攻擊。在適當情況下,仍應搭配實作 CEI 模式與重入防護鎖。
Gas 上限
由於 gas 成本取決於 Ethereum 的操作碼(opcodes),而操作碼有可能變動,因此 gas 上限並非阻止攻擊者的最佳方法。相對地,智能合約程式碼則是不可變的。
理解 send、transfer 與 call 函式之間的差異相當重要。send 與 transfer 實際上效果相同,但如果交易失敗,transfer 會還原(revert),而 send 則不會。
與 send 及 transfer 不同,call 函式沒有 gas 上限,會將其 gas 轉發以執行跨多個合約的交易。然而,這也表示重入攻擊仍有可能發生。
透過 Alchemy University 進一步了解重入攻擊
Ethereum 上智能合約的執行週期並非萬無一失。惡意人士會利用重入攻擊這種區塊鏈上的手法,從有漏洞的智能合約中轉移並掏空資金。採取預防措施,將能確保 Solidity 開發者擁有更安全的執行週期,並保護其區塊鏈協議。
若想了解 Solidity 智能合約開發的最佳實務,歡迎加入 Alchemy 免費的為期 7 週 Ethereum 開發者訓練營。這個課程原本是一門價值 3,000 美元的認證課程,如今 Alchemy University 的訓練營已成為免費學習 Solidity的首選之地。
如果開發者剛接觸開發領域,Alchemy 為期 3 週的 JavaScript 速成課程,會是開始 Ethereum 訓練營之前很好的先修課程。
相關總覽

打造區塊鏈魔法
Alchemy 結合最強大的 Web3 開發者產品與工具,並提供資源、社群與卓越的支援。


