在以太坊虛擬機 (EVM) 及 Solidity 智慧合約問世後,短短數年間內就出現了許多重入攻擊事件。 2021 年 8 月 15 日,我們發現幣安智能鏈 (BSC) 項目 Dexfolio 的 LPFarming 合約存在一個可被重入攻擊的漏洞,並透過漏洞懸賞平台 ImmuneFi 通報了這個漏洞。由於 Dexfolio 研發團隊並未在過去的 120 天內公布事後剖析報告,因此我們將在本篇文章中提供相關細節。
0x00: Dexfolio
Dexfolio 的 LPFarming 合約可讓用戶以質押資產的方式來進行流動性挖礦。 對於用戶所持有的每項資產,LPFarming 合約提供了四個公用函數處理質押任務。 例如:LPFarming.stake() 函數可讓使用者將幣安幣 (BNB) 轉入合約,並將半數的 BNB 轉換為 DEXF 代幣,用以鑄造 DEXF-WBNB LP 代幣後,質押 LP 代幣。
從上方的程式碼片段可看到,在第 555 行 newBalance 由當前餘額扣掉原始餘額 initialBalance 計算出新鑄造的 DEXF-BNB LP 代幣數量。在第 560 行,新的質押記錄被加入了 _stakes[] 陣列,作為計算挖礦獎勵的依據。
0x01:漏洞
在這四個質押函數中,stakeToken() 是一個較為特殊的函數,它可以讓使用者透過支付任意一種 ERC20 代幣來交換 DEXF-BNB LP 代幣以作為質押資產。 然而,我們發現此處並未具有防止重入的 nonReentrant modifier。由於使用者可以傳入任意的 fromTokenAddress 執行 stakeToken(),因此很多的 ERC20 函數調用 (例如 transfer()、transferFrom() 及 approve() 等) 都可能被用於劫持控制流程或重入 stakeToken()。
在上方的第 647 行程式碼中可以看到,initialBalance 備份了後續代幣交換前的餘額以便計算第 651 行新鑄造的 DEXF-BNB LP 代幣數量。然而, 649 行的 swapAndLiquifyFromToken() 調用在內部執行了 fromTokenAddress.approve(), 以利在 PancakeSwap 上交換代幣。 這使得不法分子可於原始 stakeToken() 調用的主體內嵌入另一個質押操作,導致 651 行的 newBalance 變大。 簡言之,攻擊者可能會針對同一批 LP 代幣進行雙重質押。
例如,攻擊者調用 stakeToken(10),並透過另一個帳戶及 fromTokenAddress.approve() 嵌入另一個 stakeToken(90)。 最終,第一個帳戶持有 10 + 90 = 100 個質押的 LP 代幣,而第二個帳戶則持有 90 個,後者的 90 在此處經過了重複計算。
乍看下,因為第二個帳戶必須執行某些程式碼才能重入 stakeToken(),故第 638 行的 isContract 檢查可防止重入。 然而,在 LPFarming 裡的 isContract 的實作無法涵蓋所有情況,例如在 constructor 裡就能實現繞過檢查的惡意程式碼。
0x02:漏洞利用
為利用重入漏洞,我們需要一個惡意 ERC20 合約 (Ftoken) 來劫持 approve() 調用。 一如下方的程式碼片段所示, Ftoken 透過 _optIn 開關覆寫了 OpenZeppelin ERC20 實作的 approve() 函數。當開關開啟時,創建 Exp 合約並在其 constructor 中嵌入上文提及的 stakeToken() 調用,以便避開有漏洞的 isContract 檢查。
由於 LPFarming 的 isContract modifier 僅檢查某地址對應的 extcodesize,我們可透過執行 Exp 合約中建構函數 (constructor) 裡的 LPFarming.stakeLPToken() 來繞過保護機制,如下所示。
若真的很希望避免使用合約帳戶,則須確保檢查 tx.origin == msg.sender。
此處遺漏了一個部分。由於 stakeToken() 在 PancakeSwap 將 fromTokenAddress 資產轉換成 DEXF-BNB LP 代幣,我們必須創造 Ftoken-BNB 對並增加其流動性。我們透過另一個 Lib 合約來實現此一目的。 下方的 Lib.trigger() 函數可使我們在 PancakeSwap 上創造 Ftoken-BNB 對,並將與Ftoken 數量相同的 WBNB 放入流動池中。 此外,我們也加入了一個 Lib.sweep() 方便 owner 在完成攻擊後搜刮流動池中所有剩餘的 WBNB。
備妥這三份合約後,我們就可以進行實驗以驗證我們的理論。 如下方 eth-brownie 截圖所示,我們先從 21 WBNB 開始,並部署了 Ftoken 及 Lib 合約。 如前所述,我們使用 Exp 合約的 constructor 來重新質押部分 LP 代幣並通過 LPFarming.getStakes() view function 可以觀察到 Exp 合約目前所持有的份額數量。 由於 Exp 合約需要 LP 代幣用於重新質押,但直到 Ftoken.approve() 中 Exp 才會被創造,如此處所示範的 [1],我們用 Ftoken 合約地址預先以 eth-util 計算出 Exp 地址,並且將 LP 代幣轉過去。
準備好 Ftoken 及 Lib,並以 Lib.trigger() 創造 Ftoken-BNB 對後,我們便可執行第一個 stakeToken() 發起重入攻擊。
如上圖所示,我們在 stakeToken() 調用前後均執行 Ftoken.optIn(),以便啟動及切換 Ftoken.approve() 中的劫持機制。
最後,我們用 LPFarming.emergencyWithdraw() 提取 LP 代幣的數額並轉換為 WBNB。 此外, 我們也執行 Lib.sweep() 以便獲取 Ftoken-BNB 流動池中其餘的 WBNB。 最終我們取得了 19.36 WBNB,並在 LPFarming 合約中留下 Exp 合約的質押記錄。 由於 Exp 合約已部署於 Ftoken.approve() 調用中,我們無法重新初始化合約以及在 constructor 中再次執行 LPFarming.emergencyWithdraw(),因此,攻擊者似乎無法從中獲利。 然而實際上,CREATE2 指令可使我們能夠重新初始化 Exp 合約。
0x03: CREATE2
CREATE2 指令是以太坊 Constantinople 硬分叉後的產物,能使用戶透過特定合約的位元組程式碼及 salt 值預先計算合約地址。 伴隨而來的結果是,如果合約以 SELFDESTRUCT 指令自毀,同樣的位元組程式碼及 salt 值可能會再次被部署在同一個地址上。
利用這個特性,我們可以讓 Ftoken.approve() 部署 Exp 合約並執行重入後執行 SELFDESTRUCT。之後,我們可重新部署 Exp 合約並執行 LPFarming.emergencyWithdraw() 以提取雙重質押的 LP 代幣。
上圖顯示了變更後的 Ftoken.approve()。有四個參數傳遞至 CREATE2 調用。 第一個 0 代表創造合約時支付了 0 以太幣,最後一個 0 是 salt 值,其應與重新建立合約時的值相同。 第二、第三個參數均與我們要部署的位元組程式碼相關。
除了變更後的 Ftoken.approve() 外,我們還新增了 getAddress() view 函數以預先計算 Exp 合約地址。 我們在這裡只須根據 EIP-1014,以 0xff、創建者地址及 32 位元組的 salt 值及合約程式碼 bytecode。
除了上述的修改,我們也調整了 Exp 合約,根據 token 合約所保持的狀態來質押或提取 LP 代幣,並以 SELFDESTRUCT 指令自毀的方式允許下次的重新部署操作。
在 CREATE2 的幫助下,我們的 Exp 合約便可賺取利潤,如下方截圖所示:
我們成功地在區塊高度 10181384 從 LPFarming 合約中獲取 1,558 個 LP 代幣,並將其轉換為 WBNB。
0x04:事件的時間軸與致謝
我們在 2021 年 8 月 15 日將此問題回報給 ImmuneFi 平台 [2], 且 Dexfolio 團隊已要求使用者於 2021 年 8 月 20 日提取其資產 [3]。在我們通報之後,ImmuneFi 隨即告知我們,此漏洞報告有可能是重複回報,但須與 Dexfolio 團隊確認。 由於我們已逾 90 天未能從 ImmuneFi 或 Dexfolio 得到回應,因此我們選擇以獨立研究的方式揭露細節。 在我們表示有意揭露細節後,ImmuneFi 便為這份有效的報告及我們適當的揭露流程,於 2021 年 11 月 26 日贈予我們 $1000 美元等值的以太幣 (ETH) 以作為獎勵。 此外,ImmuneFi 也協助我們聯繫了最初回報同樣漏洞的「白帽」駭客 lucash-dev。
在 lucash-dev 的協助下,我們改善了漏洞利用問題,並證實了此一發現的重要性 (亦即,不法分子可能利用此漏洞掃光 LPFraming 池)。 lucash-dev 是拯救了 Dexfolio 使用者的超級英雄!
參考文獻
[1] https://ethereum.stackexchange.com/a/63235
[2] https://immunefi.com/
[3] https://dexfolio.medium.com/staking-bug-found-follow-these-instructions-immediately-dont-worry-no-funds-lost-b5ece2df2536
關於 Amber Group
Amber Group 是一家領先全球的加密金融服務提供商,全球 24 小時全天候營運,業務遍及香港、首爾、溫哥華及全球各大主要城市。成立於 2017 年的 Amber Group 為 1,000 多家知名大型機構客戶提供服務,在 100 多個電子交易所中累計交易總額已超過 1 兆美元,資產管理規模超過 40 億美元。作為一家綜合的加密金融服務提供商,Amber Group 幫助客戶獲得流動性,賺取收益並管理各種加密資產的風險,透過提供投資靈活性和最大化回報來優化長期價值。
如欲瞭解更多,請參訪官方網站 | WhaleFin官方網站
若有產品相關問題,請聯繫 Amber 客服團隊