Web3 安全警示丨Restaking 大熱背後的風險: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 向項目方开出了以下條件:
Prisma 團隊需要進行一次在线新聞發布會;
團隊所有成員都必須現場露面和出示身份證明;
向 Prisma 的所有用戶、投資方和 Exploiter 1 表示道歉和感謝;
具體介紹本次事故裏面的問題所在:智能合約審計方是誰,以及 Prisma 將來提高安全性的計劃;
Prisma 需要承認 Exploiter 1 在這起事件中沒有任何責任,Exploiter 1 純粹是在幫助 Prisma 修復問題;
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
安全建議
通過分析本次攻擊事件,我們有如下建議:
「 委托授權要慎重 」。一是項目方需要仔細衡量項目中是否需要委托授權的邏輯,如果需要,那么一定要對相應操作進行嚴格的權限校驗以及輸入校驗,防止攻擊者利用該授權,傳入不合法的參數,篡改項目中關鍵變量。二是委托授權應該有時間限制,長期的授權,容易被用戶忽略,卻被攻擊者利用。
「 項目設置暫停機制 」。建議項目方在設計項目邏輯時,建立完善的暫停機制,以應對突發的意外事件,及時止損。
本文由 ZAN Team 的 Cara 撰寫,作者個人 X 账號@Cara6289。
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播信息之目的,不構成任何投資建議,如有侵權行為,請第一時間聯絡我們修改或刪除,多謝。