2024 年 3 月 28 日,Prisma Finance 遭遇攻擊,目前累計虧損約 1100 萬美元。

作者:Cara,ZAN Team

封面:Prisma Finance

2024 年 3 月 28 日,Prisma Finance 遭遇攻擊,目前累計虧損約 1100 萬美元。 攻擊發生后,Prisma Finance 緊急暫停了專案,並告知用戶趕緊取消委託授權(https://twitter.com/PrismaFi/status/1773371030129524957)。

攻擊簡述

Prisma Finance 是一個非託管、去中心化的,以抵押乙太坊 LST(流動性質押代幣)鑄造穩定幣的專案。 比如用戶可以通過抵押 wstETH 來鑄造 mkUSD,這個過程可以創建一個 trove,trove 可以理解為是一個記錄指定 borrower 的抵押借貸情況的寶庫,這個寶庫有一個 trove manager,用來管理寶庫的抵押物以及借貸幣(鑄造的穩定幣)。

本次的漏洞合約是 MigrateTroveZap 合約,該合約的主要功能是將使用者的抵押物從一個 trove manager 遷移到另一個 trove manager。 因此創建 trove 的 borrower 就可能會授權 MigrateTroveZap 合約對其 trove 進行操作。 然而 MigrateTroveZap 合約中的「onFlashloan」函數缺乏輸入驗證,從而允許攻擊者通過 MigrateTroveZap 合約操作其他 borrower 的 trove,將其他 borrower 的 trove 中的抵押品數量變少,但是債務不變,從而盜取其他 borrower 的抵押品。

攻擊中涉及的關鍵位址

本次攻擊涉及到多筆交易,我們僅以下面這比交易為例來對攻擊進行分析。

攻擊交易:https://etherscan.io/tx/0xe15fa959627871845f2f5bbfbd7529e6d2aff20ab14ece743f11641700bd7188

攻擊 EOA:

0x7e39e3b3ff7adef2613d5cc49558eab74b9a4202

攻擊者(合約):

0xd996073019c74b2fb94ead236e32032405bc027c

受害者(Prisma Finance TroveManager):

0x1cc79f3f47bfc060b6f761fcd1afc6d399a968b6

漏洞合約(Prisma Finance MigrateTroveZap):

0xcc7218100da61441905e0c327749972e3cbee9ee

一個被利用的 borrower,簡稱 BorrowerA:

0xcbfdffd7a2819a47fcd07dfa8bcb8a5deacc9ea8

穩定幣 mkUSD:

0x4591dbff62656e7859afe5e45f6f47d3669fbb28

質押物 wstETH:

0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0

BorrowerOperations 合約:

0x72c590349535ad52e6953744cb2a36b409542719

攻擊流程分析

1. 攻擊準備

攻擊者觀測 borrower 借貸情況,尋找抵押率較高的 borrower,在這比攻擊交易中,找到的 borrower 為 BorrowerA。

攻擊者獲取 BorrowerA 在 TroveManager 中的 collateralToken(wstETH)和 debtToken(mkUSD)的數量,分別為「824,599,953,913,164,625,273」、「598,174,188,906,400,741,697,930」,在攻擊者發起攻擊時,wstETH 的價格大概為 $4155,大概估計一下抵押率為 570%,遠遠超過了最小抵押率 MCR 110%。 這給後續攻擊創造了條件。

2. 攻擊實施

攻擊實施階段主要是攻擊者通過 mkUSD 的閃電貸服務,調用到漏洞合約 MigrateTroveZap 的「onFlashLoan」函數,「onFlashLoan」函數可以對攻擊者指定的 borrower 的 trove 進行置換,所謂置換即是先關閉這個 trove,再給 borrower 開啟一個新的 trove。 這個功能本來是用來將抵押物遷移到不同的 trove manager。 然而這個功能存在漏洞,一是它沒有校驗新開的 trove 是否和之前的 trove 具有同樣數量的抵押物,二是通過 mkUSD 的閃電貸服務,攻擊者可以操控別人的 trove。 具體的攻擊步驟如下:

1、利用 mkUSD 的閃電貸服務調用到漏洞合約 MigrateTroveZap 的「onFlashLoan」函數,並在此時傳入上述觀測好的受害者 BorrowerA 的位址以及準備新開的 trove 的抵押品數量。  攻擊者調用 mkUSD 的「flashLoan」函數進行閃電貸,借出 mkUSD 給 MigrateTroveZap(MigrateTroveZap 合約用來自動將同樣的抵押物遷移到不同的 trove manager),並在「data」參數中指定了後續操作需要用到的 BorrowerA 的位址、TroveManager 的地址、創建 trove 時抵押的 wstETH 的數量,具體傳入的參數如下所示:

mkUSD.flashLoan:

在「flashLoan」函數中,首先會給 MigrateTroveZap 鑄造「598,174,188,906,400,741,697,930」這麼多 mkUSD,然後調用 MigrateTroveZap 的回調函數「onFlashLoan」,問題就出在了這個回調函數中。

2、在 MigrateTroveZap 的「onFlashLoan」函數中對 BorrowerA 的 trove 進行更換,之所以 MigrateTroveZap 能操作 BorrowerA 的 trove,是因為 BorrowerA 對 MigrateTroveZap 進行了委託授權。 原本 BorrowerA 的 trove 抵押率比較高,但是抵押率比較高的 trove 被關閉掉,取而代之的是一個債務一樣,但是抵押率更低的 trove,這個操作能夠成功是因為更換 trove 時並沒有校驗新舊 trove 的抵押物數量是否一致,最終多餘的抵押品數量會被留在 MigrateTroveZap 中。

詳細過程如下:

在 MigrateTroveZap 的「onFlashLoan」函數中,會將指定 account 的抵押物從 troveManagerFrom 遷移到 troveManagerTo,在本次調用中,從攻擊者傳入的「data」參數中解析出來,這兩個參數均指定為了 TroveManager 的位址。

MigrateTroveZap.onFlashLoan:

攻擊者指定的 account 是 BorrowerA,因此首先會調用 BorrowerOperations 的「closeTrove」函數將 BorrowerA 的 trove 關閉掉,關閉之前會通過 modifier「callerOrDelegated」檢查 BorrowerA 是否授權調用發起者(在這裡是 MigrateTroveZap)代理它進行關閉 trove 的操作。  由於 BorrowerA 確實授權了 MigrateTroveZap,因此判斷通過,並且此時並不處於 recovery mode,因此 BorrowerOperations 最終調用 TroveManager 的「closeTrove」函數關閉 trove,在這個過程中,將 BorrowerA 的抵押品 wstETH(數量為「824,599,953,913,164,625,273」)轉給 MigrateTroveZap。 隨後 burn 掉 MigrateTroveZap 的 mkUSD,數量為「597,974,188,906,400,741,697,930」。

BorrowerOperations.closeTrove:

緊接著 BorrowerOperations 的「openTrove」函數被調用,此時傳入的參數「_collateralAmount」為攻擊者在「data」參數中指定的「192,125,967,324,963,177,654」,而「_debtAmount」為攻擊者向 mkUSD 合約閃電貸的數量加上閃電貸的手續費,數值為「598,712,545,676,416,502,365,458」。 經過計算,現在並不處於 recovery mode,因此最終用來計算抵押率的 debt 的數量「compositeDebt」為「_debtAmount」+ debt borrowing 費 + debt gas 補償,具體數值為「598,912,545,682,977,901,520,496」,根據「_collateralAmount」、「_ debtAmount」和價格計算出來的 ICR(Individual Collateral Rate)約為 133%,大於 MCR(最小抵押率)110%,符合創建 trove 的條件。 隨後 MigrateTroveZap 給 TroveManager 轉移「_collateralAmount」數量的 wstETH,MigrateTroveZap 拿到給其鑄造的「_debtAmount」數量的 mkUSD。 這番操作相當於攻擊者利用 MigrateTroveZap 合約,將 BorrowerA 本來有很多抵押品的 trove,換成了抵押品較少的 trove,而攻擊者想要盜取的,正是這一部分抵押品差額。

BorrowerOperations.openTrove:

3、回調結束以後,回到 mkUSD 的 flashLoan 函數。 此時 MigrateTroveZap 持有的 mkUSD 的數量正好比需要償還的 mkUSD 的數量多一些,通過 burn 掉 MigrateTroveZap 的 mkUSD 成功償還了閃電貸的債務。 至此,MigrateTroveZap 相當於額外獲得了約 632 個 wstETH。 接下來攻擊者就開始想辦法把這 632 個 wstETH 給套到自己手上了。

3. 收割贓款

收割贓款的步驟主要是攻擊者將 #攻击实施 #步骤中盗取的留在 MigrateTroveZap 合約中的 wstETH 提取出來。 攻擊者依然是利用 MigrateTroveZap 合約的漏洞。 這一次攻擊者先自己開了一個抵押率較低的 trove,然後通過 mkUSD 的閃電貸服務進入到漏洞合約 MigrateTroveZap 的「onFlashLoan」函數,對自己的 trove 進行更換。

相較於 #攻击实施 #阶段对 BorrowerA 的 trove 的更換,不同之處在於,攻擊者對自己 trove 的更換,是要從低抵押率的 trove 更換為高抵押率的 trove,而用到的抵押物正是 MigrateTroveZap 中多出來的 wstETH。 之所以能用到 MigrateTroveZap 的 wstETH,是因為攻擊者也授權 MigrateTroveZap 對其 trove 進行管理,那麼在更換 trove 時,抵押物都是在 TroveManager 和 MigrateTroveZap 之間流轉。 所以在開一個新的 trove 時,如果用到的抵押物數量增多,就會直接扣除 MigrateTroveZap 中的抵押品數量。

最終攻擊者的 trove 在更換之後擁有了更多的抵押物,攻擊者就可以自己發起關閉 trove 的調用,將抵押物提取出來,從而將盜取的抵押物收入囊中。

詳細過程如下:

1、攻擊者從 Balancer 中閃電貸出 1 個 wstETH,從而調用到攻擊者的「receiveFlashLoan」函數。

2、在「receiveFlashLoan」函數中,攻擊者首先授權 BorrowerOperations 使用其 wstETH,然後再授予 MigrateTroveZap 代理權。

3、接著攻擊者調用 BorrowerOperations 的「openTrove」函數給自己創建一個 trove,使用閃電貸出的 1 個 wstETH 進行抵押,借 2000 個 mkUSD,此時的抵押率大約為 188%。 這是攻擊者為了後續操作做的一個準備。 傳入的參數詳細資訊如下:

4、攻擊者再次調用 mkUSD 合約的 flashLoan 函數進行閃電貸,這一次是借 2000 個 mkUSD 給 MigrateTroveZap,跟 #攻击实施 #阶段类似,給 MigrateTroveZap 鑄造了 2000 個 mkUSD,並觸發了 MigrateTroveZap 的回調函數「onFlashLoan」。 這一次攻擊者傳入的「data」參數如下,攻擊者指定的想要遷移的 trove 是攻擊者上一步創建的 trove。

5、在函數「onFlashLoan」里,同樣先執行「closeTrove」函數,TroveManager 將 1 個 wstETH 轉給 MigrateTroveZap,然後 burn 掉 MigrateTroveZap 約 2000 個 mkUSD。 隨後執行「openTrove」函數,這一次攻擊者指定的「_collateralAmount」值為「633,473,986,588,201,447,619」,幾乎是 MigrateTroveZap 中所有的 wstETH,當然其中包括了在 #攻击实施 #阶段套取的约 632 個 wstETH。 此時「_debtAmount」具體值為「2,001,800,000,000,000,000,000」。 抵押率大概為 1195%。 trove 創建成功,MigrateTroveZap 將約 633 個 wstETH 轉給 TroveManager,並給 MigrateTroveZap 鑄造了約 2001 個 mkUSD。

6、回調結束以後,回到 mkUSD 的 flashLoan 函數。 MigrateTroveZap 償還閃電貸債務,持有的 mkUSD 被 burn 掉。

7、到這一步,攻擊者自己創建的 trove 依然存在,不同的是,經過前面幾步在 mkUSD 中的閃電貸,攻擊者將自己的 trove 中抵押品的數量提升到了約 633 個 wstETH。

8、攻擊者調用 BorrowerOperations 的「closeTrove」函數,關閉 trove,從 TroveManager 處拿到約 633 個 wstETH,並 burn 掉相應的 mkUSD 債務。

9、攻擊者償還在 Balancer 中借出的 1 個 wstETH。

至此,收割贓款結束,攻擊者獲利約 632 個 wstETH。

資金流追蹤

本次攻擊共涉及到三個 EOA,如下:

  • Exploiter 1: 0x7E39E3B3ff7ADef2613d5Cc49558EAB74B9a4202
  • Exploiter 2: 0x7Fe83f45e0f53651b3ED9650d2a2C67D8855e385
  • Exploiter 3: 0x7C9FC6E2B908e858F30c5c71a20273315Efd5cf8

Exploiter 2 和 Exploiter 3 共獲利約 200 個 ETH,而另外的約 3200 個 ETH 則是被聲稱是白帽的 Exploiter 1 獲取(https://etherscan.io/tx/0xc2825fd6dd05e8ec9f271d63efdebd06e78296afc0813c65788790567916d209)。 目前 Prisma Finance 專案方仍在與聲稱是白帽的 Exploiter 1 溝通資金退還事宜。

Exploiter 1 向專案方開出了以下條件:

  1. Prisma 團隊需要進行一次在線新聞發佈會;
  2. 團隊所有成員都必須現場露面和出示身份證明;
  3. 向 Prisma 的所有使用者、投資方和 Exploiter 1 表示道歉和感謝;
  4. 具體介紹本次事故裏面的問題所在:智慧合約審計方是誰,以及 Prisma 將來提高安全性的計劃;
  5. Prisma 需要承認 Exploiter 1 在這起事件中沒有任何責任,Exploiter 1 純粹是在説明 Prisma 修復問題;
  6. Prisma 需要在 12 小時內修改事後總結中所有具有指控性的措辭。

通過 ZAN 的 KYT 服務,我們可以看到,Exploiter 1 將資金轉到了三個不同的位址上,最終這些資金一部分流入了 Tornado Cash 中。 詳細資金流轉情況請查看連結:https://zan.top/kyt/controller/transaction?entity=0x7e39e3b3ff7adef2613d5cc49558eab74b9a4202&ecosystem=ethereum

轉入 Tornado Cash 資金流動情況

Exploiter 2 的資金流轉情況:https://zan.top/kyt/controller/transaction?entity=0x7fe83f45e0f53651b3ed9650d2a2c67d8855e385&ecosystem=ethereum

Exploiter 3 的資金流轉情況:https://zan.top/kyt/controller/transaction/?entity=0x7C9FC6E2B908e858F30c5c71a20273315Efd5cf8&ecosystem=ethereum

安全建議

通過分析本次攻擊事件,我們有如下建議:

  1. 「委託授權要慎重」。 一是專案方需要仔細衡量專案中是否需要委託授權的邏輯,如果需要,那麼一定要對相應操作進行嚴格的許可權校驗以及輸入校驗,防止攻擊者利用該授權,傳入不合法的參數,篡改專案中關鍵變數。 二是委託授權應該有時間限制,長期的授權,容易被使用者忽略,卻被攻擊者利用。
  2. 「項目設置暫停機制」。 建議專案方在設計項目邏輯時,建立完善的暫停機制,以應對突發的意外事件,及時止損。

免責聲明:作為區塊鏈資訊平臺,本站所發佈文章僅代表作者及嘉賓個人觀點,與 Web3Caff 立場無關。 文章內的資訊僅供參考,均不構成任何投資建議及要約,並請您遵守所在國家或地區的相關法律法規。