黑客利用 Akropolis 合約漏洞連續實施了 17 次重入攻擊,導致其 yCurve 和 sUSD 資金池損失了 203 萬枚 DAI。

原文標題:《DeFi 協議 Akropolis 漏洞詳解:黑客復現「經典重入攻擊」擄走 203 萬 DAI》
撰文:PeckShield

昨天晚間 19 時 50 分,DeFi 協議 Akropolis 遭到了黑客攻擊。

區塊鏈安全公司 PeckShield (派盾)安全人員迅速定位到問題在於,Akropolis 項目的 SavingsModule 合約在處理用戶存儲資產時存在某種缺陷,黑客利用此缺陷連續實施了 17 次重入攻擊,導致其 YCurve 和 sUSD 資金池損失了 203 萬枚 DAI。

PeckShield:技術拆解 DeFi 協議 Akropolis 被盜 200 萬美元攻擊過程

技術概要

本次攻擊的原因如下:

  1. 合約沒有對用戶存儲的 Token 進行白名單校驗
  2. 關鍵的 deposit 函數沒有對重入攻擊的保護

簡單而言:黑客利用 Akropolis 項目存在的存儲資產校驗缺陷,向合約發起連續多次的重入攻擊,致使 Akropolis 合約在沒有新資產注入的情況下,憑空增發了大量的 pooltokens,進而再利用這些 pooltokens 從 YCurve 和 sUSD 池子中提取 DAI,最終導致項目合約損失了 203 萬枚 DAI。

攻擊過程詳解

攻擊流程復現:

我們通過分析黑客實施攻擊的交易哈希
(0xe1f375a47172b5612d96496a4599247049f07c9a7d518929fbe296b0c281e04d)
發現,攻擊來自於一個惡意的 ERC20 合約地址
(0xe2307837524Db8961C4541f943598654240bd62f)

這個惡意合約實現了一個鉤子函數,使得函數在 transferFrom() (function signature: 0x23b872dd) 被調用的時候會被執行。

攻擊者先是調用 SavingsModule.sol 中的 deposit() 函數,並將自己編寫的位於 0xe230 開頭的惡意合約作爲要存儲的代幣地址傳入。當其惡意代幣的 transferFrom() 函數被調用時,其鉤子函數會再次調用 deposit() 函數並存入真實的 DAI 資產。

由於 pooltokens 增發的量通過代幣 deposit 前後餘額的差值得出。所以第二次 deposit 的真實的 DAI 資產會被計算兩次用於鑄造 pooltokens。第一次是在惡意合約 0xe230 存儲的時候,第二次是在 DAI 存儲的時候。也就是說,如果第二次存儲的時候存入了 25K DAI, 那麼由於重入攻擊,總的鑄造的 pooltokens 將會是雙倍,也就是 50K DAI 。

PeckShield:技術拆解 DeFi 協議 Akropolis 被盜 200 萬美元攻擊過程

以此類推,黑客總共發起 17 次重入攻擊並獲得了總共 2,030,841.0177 個 DAI 資產。

值得注意的是,在攻擊的最開始,攻擊者還使用了 dYdX 的 閃貸功能

核心漏洞詳解

接下來,我們分析下存在漏洞的代幣存儲邏輯。Akropolis 的用戶可以將代幣存儲入 Delphi Savings Pools,而資金池會鑄造相應的 pooltokens 給用戶。核心邏輯在 SavingsModule::deposit()(1,944 行)。

PeckShield:技術拆解 DeFi 協議 Akropolis 被盜 200 萬美元攻擊過程

第一步:攻擊者調用 deposit() 函數並提供 _tokens 參數。這個函數在進一步調用 depositToProtocol(_protocol,_tokens,_dnAmounts) 前後會計算代幣的餘額,並通過代幣餘額的變化來決定將要鑄造的 poolTokens 數目 (第 1,970 行)。而 depositToProtocol() 函數會調用目標代幣的 safeTransferFrom() 函數來進行代幣的轉賬 (第 2,004 行)。然而 deposit() 函數沒有對重入攻擊進行檢測,也沒有檢查存入的代幣是否爲惡意代幣;

第二步:在惡意代幣的 transferFrom() 函數被調用的時候,觸發鉤子函數,從而再次調用 deposit() 函數;

第三步:因爲第二次調用 deposit() 函數的時候攻擊者存入了真正的 DAI 代幣使得池子的代幣餘額發生變化,所以攻擊者可以獲得資金池鑄造的 poolTokens;

第四步:當第二次 deposit() 函數調用結束的時候,代碼執行流程將返回第一次存儲代幣調用 depositToProtocol() 函數的上下文。這個時候,代幣餘額變化將被再次計算。此時代幣餘額的變化和第二次調用 deposit() 函數代幣餘額變化一樣。因此攻擊者可以再次獲得相應數目的 poolTokens。

被盜資產情況

這次攻擊的被盜資產目前被存儲在錢包 0x9f26 中。PeckShield 旗下數字資產追蹤平臺 CoinHolmes 正在對該地址做全方位監控,並對其資金流向做進一步的鎖定分析和追蹤,以便協助項目方挽回被盜資產。

來源鏈接:PeckShield 派盾