Cream Finance 安全事件深度分析

買賣虛擬貨幣

2021年8月30日,Cream Finance遭受攻擊,損失超過1000萬美金。對於本次安全事件,網上已經有一些分析指出這是一次利用閃電貸的重入攻擊。然而這些分析僅針對攻擊交易本身,其中還存在一些問題尚未釐清,比如這一次的重入攻擊與先前的經典重入攻擊有何區別,攻擊者為什麼透過清算獲利等。對這些問題的背後原因進行探究,有助於幫助我們更好地理解該類DeFi安全威脅,並採取應對措施。

問題1:本次重入攻擊和之前的重入有什麼核心不同點?重入漏洞的本質究竟是什麼?

Cream合約的漏洞成因仍然是重入,但是本次重入的最核心問題是多合約之間的重入。我們認為重入漏洞中的重入其實是在描述利用方式,而非刻畫漏洞本質。重入漏洞究其本質可以建模成risky access,即:a)存在著某一個共享的狀態;b)存在著至少兩個操作可透過external function改變共享的狀態;c)至少一個操作是寫操作。在滿足上述條件的情況下,我們認為存在著一個risky access。而在這樣的定義下,共享的狀態可以在出現在同一個函式,同一個合約,甚至是不同的合約。而如何利用這樣的risky access來發起攻擊,就需要進行重入了(透過重入實現第二個條件)。

問題2:攻擊者透過攻擊獲得的AMP token,如何實現套現獲利?

攻擊者透過重入獲得是AMP token和一部分虧空的ETH,攻擊者需要將AMP token套現來獲得盈利和還掉閃電貸。攻擊者可以選擇多種方式進行,比如在DEX中用AMP token換取ETH和在Cream中進行清算兩種方式套現。這兩種套現的方式各有利弊。我們認為清算是一種獲利方式更高的套現手段,可以避免大量在DEX中用AMP token換取ETH引起的價格波動和滑點。

問題3:還有哪些鏈上機制被攻擊者利用實施攻擊?

我們發現本次攻擊中,FlashBot被攻擊者濫用以賄賂曠工,從而獲得快速上鍊權。對於FlashBot的安全隱患,我們曾經在之前釋出的文章中進行過討論(具體可參考由xSNXa事件引發的對FlashBot的思考),而本次攻擊事件進一步證實了FlashBot對於區塊鏈生態的安全影響,需引起重視並引入機制來對FlashBot交易進行管理。正如菜刀,用來切菜是無害的,用來行兇則成為作案工具。

以下我們將圍繞上述三個問題,從多合約重入、套現獲利方式和FlashBot被濫用三個角度對本次攻擊開展深度分析。

0x1. 多合約重入

ERC777標準相比ERC20,包裝了諸多特性,其中之一便是:一筆轉賬過程中引入兩次回撥,分別在餘額增加前後(TokensToSend, TokensReceived)。具體實現機制是使用了 ERC1820 標準的序號產生器制,轉賬相關合約如果實現了相應的介面函式並在ERC1820合約中註冊過,便會在轉賬發生時,分別去呼叫代幣減少一方 (Holder) 的 TokensToSend 函式和代幣增加一方 (Receiver) 的 TokensReceived 函式。

重入攻擊是由於某些不可信的外部呼叫,打斷了一些原子性的事務 (比如轉賬和記賬),造成變數狀態不一致而產生的。

對於一般的ERC20代幣,在轉賬的過程中是不涉及外部呼叫的。我們可以假設不會影響合約的狀態,但是對於ERC777這一假設便不成立了。因此很多DeFi協議由於引入ERC777代幣,而造成嚴重的安全事故(比如:UniswapV1, Lendf.Me等)。

0x1.1 攻擊形式和邏輯變得日益複雜

隨著Defi生態的不斷成熟繁榮的同時,我們也可以看到,攻擊的形式與邏輯也越來越複雜。

以重入攻擊為例:從最初比較簡單的在函式轉賬後重入轉賬函式,導致最終記賬結果不正確(比如:The DAO, Lendf.Me事件)。現在大部分的合約都會採用鎖的機制(nonReentrant等)來避免。到利用轉賬前後計價公式中間狀態的不一致**,重入獲利(比如:UniswapV1)。

但是,這種鎖的機制真的可以一勞永逸嗎?事實並非如此。nonReentrant 可以保證函式以及合約層面無法被重入,但是對於依賴關係複雜的 Defi 協議,一個變數並非僅僅影響一個合約。這時,原本存在重入攻擊可能的地方,即使有了 nonReentrant 的保護,我們是否也可以採用一種聲東擊西的方式。來在其他依賴該變數的地方做一些手腳呢?本次的事件也是如此。

0x1.2 具體程式碼解讀

可以看到,使用者從Cream借錢時的函式呼叫大致為:

borrow → borrowInternal → borrowFresh

在最內層的 borrowFresh中,我們可以看到,這似曾相識的程式碼,便是一年之前,Lendf.Me倒下的地方。

在 Transfer 前會使用臨時變數vars記錄轉賬後的一些結果,transfer 結束後再更新到借貸池的全域性狀態變數中。

雖然Cream吸取了前輩的教訓,在這個函式中使用了 nonReentrant,這保證了該借貸池的合約是無法被重入的。但是,遺憾的是,一個交易池交易池的狀態,並不僅僅關係到自己。

程式碼中我們可以看到,在進入borrowFresh前,會先呼叫 borrowAllowed函式,判斷使用者是否有資格進行借款。這一資格指的是使用者的賬務情況。如果使用者賬面有足夠的流動性,便可以借出相應數量的資產。而這一流動性並不僅僅是使用者在一個借貸池中的借貸情況,而是使用者在 Cream 平臺全部借貸池中的累計:

可以看到borrowAllowed呼叫了getHypotheticalAccountLiquidityInternal函式來計算使用者的流動性,看使用者是否已經資不抵債。

在getHypotheticalAccountLiquidityInternal中,會遍歷使用者所在的不同借貸池,透過 getAccountSnapshot函式,獲得使用者借出的底層資產 (Underlying Token) 數量。這樣,利用ERC777重入導致使用者其中一個借貸池狀態未更新。便可以實現「我借了,但我就說我沒借」這樣一種效果,實現超額借貸。

重入攻擊的流程如下:

Step 1: 利用 Flash Swap 從 Uniswap 借出 500 ETH

Step 2: 將借出的 ETH 抵押到 Cream,呼叫crAMP池中的borrow函式,換取全部抵押ETH價值45%數量的AMP token(19,480,000,000,000,000,000,000,000)

注意!到這一步,使用者在Cream已經沒有流動性,本不應再借出任何代幣,但是由於crAMP池中使用者的借貸狀態尚未更新,使得使用者在外部呼叫中還擁有全部的流動性。

Step 3:在AMP transfer的回撥函式中,呼叫crETH池中的borrow函式,再次換取全部抵押ETH價值75%數量的ETH (355 ETH)

可以看到,這一步攻擊者獲得了原抵押物45% + 75% = 120%價值的代幣。

0x2. 獲利處理

我們在上一步重入攻擊中已經產生了獲利,但是獲利一部分是AMP token,此時的持有的ETH是不足以還上UniswapV2的閃電貸的,那麼怎麼才能將AMP套現獲得足夠的ETH?

方法1:還crETH或者crAMP的借款,從而取出一開始的抵押物ETH。但是由於在重入攻擊結束之後,同時修改了crETH和crAMP兩個合約的狀態。一旦需要還款取出全部抵押物,Comptroller會在檢查這兩個合約的借賬清零後再同意。這將導致所有重入的獲利重新回到Cream.Finance手上,因此是不行的。這也是攻擊者為什麼不直接償還借貸來獲利的原因。

方法2:將獲得的AMP token在去中心化交易所(如Uniswap)換成ETH。這種做法是可行的,而且事實上攻擊者其中幾筆攻擊交易中確實採取了這種做法(詳見下文表格)。但是這種做法有個缺陷,由於攻擊獲得的AMP的量較大,而大量的AMP token流進Uniswap池子會導致滑點(即用同樣的AMP token換出來的ETH量減少),導致攻擊獲利減少。

方法3:新建一個地址,用該地址清算實施重入攻擊的攻擊者的負債。由於Cream Finance的清算實現中,其清算折扣是一個固定值(即清算者能以一定的折扣購買到抵押品,見圖中清算所得的計算),且只要超額抵押率低於某一定值就可以執行。而在上一步重入攻擊之後,由於攻擊者借出的ETH和AMP兩種資產的總價值超出了抵押的ETH的價值,這個時候滿足了清算條件。所以新建的地址可以以一定的折扣獲得前面攻擊地址的部分抵押物,即ETH(透過redeem crETH)。該方法不僅沒有去中心化交易所的滑點問題,還由於清算折扣的存在增加了攻擊獲利。但是能清算的資產是由一個最大額度(50%),這也是我們在攻擊中最後看到攻擊者只用了一半的AMP進行了清算(具體的清算邏輯可參考 liquidateCalculateSeizeTokens)。

0x3. FlashBot被濫用

FlashBot機制使得礦工可以將快速上鍊的機會進行拍賣,從而避免套利者之間的競爭導致的以太坊網路擁塞和交易費上升,其在套利者和礦池之間找到了利益的平衡點,因此被礦池大量採用。然而,FlashBot的出現也導致了另外的問題,即被攻擊者利用將攻擊交易快速上鍊,使得礦工成為攻擊者的幫兇。我們對本次攻擊的攻擊者進行了詳細的統計,我們發現將近一半的攻擊交易是FlashBot交易。另外,我們也發現攻擊是由兩個攻擊者發起,攻擊總獲利超過5800ETH,詳細資訊見下面表格。

攻擊者

交易

是否為

Flashbot交易

獲利方式

獲利

0x8036ebd0...

0xc90468d6...

否用借出AMP總量的一半進行清算,剩下一半取出

9,740,000 AMP,

36.9945 ETH0x3ab23685...否將第二批攻擊獲得的AMP在1inch交易所兌換為ETH-9,740,000 AMP, 131.6135 ETH

0xce1f4b4f...

0x00167456...否

在Uniswap兌換部分AMP,其餘取出

2,246,331 AMP0x77e2d72b...否在Uniswap兌換部分AMP,剩下的直接取出16,564,396 AMP0x55692ccc...是(攻擊者可能利用了Flashbot)在Uniswap兌換部分AMP歸還Flash Loan,其餘取出18,198,820 AMP0x51fd8340...是(攻擊者可能利用了Flashbot)在Uniswap兌換部分AMP歸還Flash Loan,其餘取出12,626,794 AMP0xf5a3225f...是(攻擊者可能是Front-running受害者)在Uniswap兌換部分AMP歸還Flash Loan,其餘取出11,759,107 AMP0x8b4ec34b...是(攻擊者可能利用了Flashbot)在Uniswap兌換部分AMP歸還Flash Loan,其餘取出16,016,121 AMP0xcb16bb40...是將第一批攻擊獲得的AMP在1inch交易所兌換為ETH-77,411,571 AMP, 887.0617 ETH0xba468e26...是(攻擊者可能利用了Flashbot)在Uniswap兌換部分AMP歸還Flash Loan,其餘取出1,397,977 AMP0xd7ec3046...否用借出AMP總量的一半進行清算,剩下一半取出19,480,000 AMP, 69.5827 ETH0x487364b6...否用借出AMP總量的一半進行清算,剩下一半取出38,960,000 AMP, 148.7233 ETH0x6f3bc128...是(攻擊者可能利用了Flashbot)用借出AMP總量的一半進行清算,剩下一半取出77,920,000 AMP, 297.4470 ETH0xf0f6a07e...是(攻擊者可能利用了Flashbot)用借出AMP總量的一半進行清算,剩下一半取出77,920,000 AMP, 297.4472 ETH0x5452e5ff...是(攻擊者可能利用了Flashbot)用借出AMP總量的一半進行清算,剩下一半取出38,960,000 AMP, 148.7240 ETH0x6afb3e8e...否用借出AMP總量的一半進行清算,剩下一半取出38,960,000 AMP, 148.7243 ETH0x1c346413...是(攻擊者可能利用了Flashbot)用借出AMP總量的一半進行清算,剩下一半取出19,480,000 AMP, 78.2063 ETH0x1d20ea65...否用借出AMP總量的一半進行清算,剩下一半取出19,480,000 AMP, 78.2065 ETH0xa9a1b8ea...是(攻擊者可能利用了Flashbot)用借出AMP總量的一半進行清算,剩下一半取出9,740,000 AMP, 41.0334 ETH0x7df47235...否將第二批攻擊獲得的AMP在1inch交易所兌換為ETH-102,297,977 AMP, 1,164.8246 ETH0xc464fed4...否將第二批攻擊獲得的AMP在1inch交易所兌換為ETH-100,000,000 AMP, 1,083.1151 ETH0xe15324ef...否將第二批攻擊獲得的AMP在1inch交易所兌換為ETH-140,000,000 AMP, 1,282.6449 ETH

攻擊者0x8036ebd0總計獲利168.608 ETH;攻擊者0xce1f4b4f總計獲利5725.741 ETH。

0x4. 結語

透過對Cream Finance安全事件的總結,我們對於該類安全威脅有了更深入的理解:重入問題最本質的原因在於對共享狀態的risky access,形式可以是同一個合約的同一個函式,同一個合約的多個函式甚至是不同合約的函式,因此單純依賴nonReentrant鎖機制不能防止重入攻擊,需要對合約邏輯層面的狀態變化進行精確的分析和安全審計。

FlashBot對於生態的影響值得重視:Flashbot可能被濫用於攻擊交易快速上鍊,成為攻擊者的幫兇。這需要FlashBot服務提供者、礦工和安全社羣的協作,構建DeFi生態安全共同體。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读