BlockSec DeFi 攻擊系列之五以假亂真:DODO V2 眾籌池造襲事件分析
BlockSec
2021-08-08 10:05
本文约6480字,阅读全文需要约26分钟
進入以太坊(Ethereum)這片黑暗森林,隱藏於各處的攻擊者和套利者也得做好成為獵物的準備......

去中心化金融(DeFi) 作為區塊鏈生態當紅項目形態,其安全尤為重要。從去年至今,發生了幾十起安全事件。

BlockSec 作為長期關注DeFi 安全的研究團隊(https://blocksecteam.com),獨立發現了多起DeFi 安全事件,研究成果發佈在頂級安全會議中(包括USENIX Security, CCS 和Blackhat)。在接下來的一段時間裡,我們將系統性分析DeFi 安全事件,剖析安全事件背後的根本原因

往期回顧:

(1) [BlockSec DeFi 攻擊分析系列之一] 我為自己代言:ChainSwap 攻擊事件分析

(2) [BlockSec DeFi 攻擊分析系列之二] 傾囊相送:Sushiswap 手續費被盜

(3) [BlockSec DeFi 攻擊分析系列之三] 偷天換日:深度剖析Akropolis 攻擊事件

一級標題

0x0. 前言

北京時間2021 年3 月9 日凌晨,以太坊(Ethereum) 去中心化交易平台DODO 的多個V2 眾籌池遭到攻擊,攻擊者利用眾籌池的合約漏洞,用自己創建的token 套出了池內的token,上演了一出狸貓換太子的把戲。只不過,從之後的分析來看,事情的發展顯然偏離了攻擊者的預期軌道……

一級標題

一級標題

0x1. 事件回顧

二級標題

No code, no bug

二級標題

0x1.1 概念簡析

提了這麼多次DODO V2 眾籌池,那它到底是個啥玩意兒?簡單來說呢,它就是個資金池,只不過本文中的資金所指代的並非我們日常接觸到的法幣,而是以太坊上的代幣(ERC20 token)。這種資金池在初始化的時候會設定兩種鏈上有價值的ERC20 token 作為這個池子的token pair (記住這個點,之後要考的),然後用戶可以正常使用這些池子提供的服務。在這次小A 針對DODO V2 眾籌池的攻擊中,就是利用了該池子提供的閃電貸(flashloan)服務。

閃電貸(flashloan) 相比於普通借貸,具有無需抵押即可藉貸大量資金,加之還不上錢就狀態回滾的特點。簡單來說就是可以空手套白狼,要是最後藉出的錢自己一通操作之後虧了還不上也不打緊,所有相關狀態會回滾到借貸之前,而對於借貸者來說唯一的損失除了手續費之外,可能就只有時間了。使用閃電貸的邏輯也很簡單,就是用戶從資金池借錢--> 對借來的錢進行操作--> 還錢,而這些需要在一筆交易中完成。

Ok,簡單了解完相關的概念之後,咱接著看小A 的操作。

0x1.2 小A 的騷操作

小A 接下來做了兩件事兒。

第一件事兒就是創建了兩個ERC20 token 的合約,說白了就是造了倆假幣( 因為沒價值),分別叫做FDO 和FUSDT. 創建完這倆假幣之後呢小A 就把它們存入到之後將被攻擊的DODO 池子裡了。

小A 做的第二件事兒就是使用了DODO V2 眾籌池的閃電貸(flashloan) 服務。上文提到閃電貸使用邏輯中的一環就是對借來的錢進行操作,而本文的主角小A 就是在這一環節上玩兒出了花樣。

小A 發現這個DODO V2 眾籌池是可以被重新人為初始化的,而且沒什麼限制。接著小A 就在藉出錢之後把這個池子重新初始化了,並且初始化時設定的token pair 變成了小A 自己造的倆假幣FDO/FUSDT (這就是之前提到的考點,現在考到了吧)。並且小A 在藉錢之前已經通過上文他做的第一件事兒把足量的兩種假幣都預先存入到了這個池子裡,這就導致池子在檢查小A 欠錢情況的時候,所檢查的token 不再是之前的真幣,而是小A 存入其中的假幣,加之數額也足夠。這樣一來小A 就用假幣套出了真幣。

咱們可以對上述過程打個簡單的比方,細節按下不表。就是有一家銀行,這個銀行就可以類比上文提到的資金池。同時該銀行也有一個小本本拿來記賬,只不過在小本本的首頁有一條記錄【本行只處理人民幣,儲備量100】 。至於其他的幣種,美元也好,越南盾也好。用戶也可以往裡面存,但銀行不管你,它的小本本上雖然也會記賬,但核對時它只看涉及到人民幣的記錄。

為了類比眾籌池可以被重新初始化的問題,可以這麼理解。就是這個小本本有個問題,問題出在首頁的這句【本行只處理人民幣】的規定是用鉛筆寫的,並且這個小本本就放在窗口,外人拿個橡皮擦+鉛筆就能改。然後這個問題被小A 發現了,他先往這個銀行存了100 越南盾,銀行會記賬【小A 往本行存入100 越南盾】。然後小A 又去銀行借出100 人民幣,銀行又記賬【小A 從本行借出100 人民幣】。注意銀行會檢查所有用戶對人民幣的借存情況並和儲備量比對,從而確認是否有用戶借了人民幣但沒還。

剛借完錢,小A 就雞賊地掏出準備好的鉛筆+橡皮將小本本首頁的規定改為了【本行只處理越南盾,儲備量100】 。然後銀行核對金額的時候,因為規定改為了只處理越南盾,而查驗的時候發現越南盾的儲備量和小A 存入的越南盾數額是一樣的,說明沒人欠銀行錢。然後小A 拿著借出來的人民幣瀟灑離去,不再回頭。

小A 針對DODO V2 眾籌池的攻擊思路就如上文所述,小A 接下來要做的就是把想法付諸實際行動。本文從合約代碼層面要分析的攻擊也就是上述提到的攻擊,然而咱的故事還沒完。不想接著看故事的看官可以直接下滑到0x2 的攻擊分析部分,剩下的看官,就請容我接著給您講故事。

0x1.3 攻擊過程:山路十八彎

第一回. 螳螂捕蟬黃雀在後

咱書接上文。

項目方也就是DODO 採用相同的邏輯創建了多個池子,而這些池子都有同樣的漏洞。小A 第一次瞄準的池子是(WSZO/USDT)池,WSZO 和USDT 就是兩種有價值的代幣( ERC20 token)。他美滋滋的把自己的攻擊邏輯寫入一筆交易之後,就準備躺著收錢,可就在這時,出岔子了。

咱知道,以太坊上的交易存在交易費,這筆費用是用戶支付給礦工的。所以礦工為了自身利益最大化,當他們從pengding 池打包交易準備上鍊時會查看各條交易的交易費並排個序,交易費高的優先上鍊。而由於以太坊的去中心化機制,黑暗森林法則在這也同樣適用。 DeFi(去中心化金融)項目的攻擊者、套利者抑或搶跑者潛伏在世界各地,一旦發現目標,就會一擁而上,至於最後誰能成功,除了技術等因素,就得看誰出的交易費高了。

回到主角小A,他發現了漏洞準備趁機搞一波錢,可小A 的舉動被另一個角色--搶跑機器人--發現了,咱稱這個搶跑機器人為小B 吧。小B 就在交易費上做了文章,他把交易的油費(gas price)提高了,從而增加了這筆交易的交易費。這就使得礦工在打包交易的時候優先打包了小B 的交易。一般把小B 的這種操作叫做搶跑交易(front running)。等輪到打包小A 交易的時候,WSZO/USDT 池子裡的WSZO 和USDT 已經空空如也,只剩下一堆沒啥價值的(FDO/FUSDT)。

小A 可能以為這一次的失敗只是一次普通的搶跑交易,於是他又故技重施。只不過這次的目標換成了(ETHA/USDT)池,而且為了防止被搶跑,在這一次的交易中小A 提高了交易的油費(gas price)。可小A 不知道的是,他已經被小B 盯上了。

同樣的套路,同樣的手法,小A 又一次被小B 搶跑了交易。

第二回. 半路殺出個程咬金

到目前為止,小A 兩次攻擊都是給小B 做了嫁衣,甚至自己還虧了交易費!小A 那個氣啊,於是痛定思痛,把目標瞄向了下一個池子(wCRES/USDT) 。並且這一次小A 成功的繞過了小B 的監控。事情發展到現在似乎小A 就要成功了,可現實就是喜歡和小A 開玩笑。

第三次攻擊雖然繞過了小B,但小A 把錢成功取出來之後並沒有直接轉移到自己的地址,而是創建了一個新合約來接收這筆錢。小A 的想法是先把錢暫存在這個新合約內,之後再轉移到自己的賬戶中。然而,小A 的這個用來暫存贓款的合約又有個漏洞,就是任何人都能從這個合約中取出(withdraw) 合約內的資金。

However, this contract had a loophole that allowed anyone to withdraw assets from it.

而恰巧這個漏洞,被另一個角色(小C) 發現了。於是,在小A 發出一筆交易準備從這個合約中取回贓款的時候,又雙叒被搶跑了,這不過這一次搶跑者變成了小C。

看到這,小A 既是攻擊者,又是搶跑交易的受害者。這波啊,小A 算是偷雞不成蝕把米。

第三回. 倔強的小 A

前文提到,具有同樣邏輯漏洞的池子有多個。小A又雙叒叕準備發起他的第四次攻擊了,這一次,目標換成了(DODO/USDT)池。這一次小A 學聰明了,一方面他將暫存贓款的合約替換為了新合約,另一方面小A 也將代幣(token) 轉移的過程合併在了一筆交易中,以免出現像第三次攻擊中被小C 搶跑的情況。而這一次,不撞南牆不回頭的小A 終於成功用假幣(FDO/FUSDT) 搞到了真錢(DODO/USDT)。

第四回. 瘋起來連自己都咬

四次攻擊終獲成功之後小A 滿足了嗎?事實告訴我們答案是否定的。

不知道小A 是怎麼想的,可能是歷經曲折終於成功導致興奮過頭。這第五次攻擊,他的目標瞄向了(wCRES/USDT) 池。是不是有點眼熟?沒錯!這個池子正是小A 在第三次攻擊中的目標池子。而經過第三次攻擊的洗劫之後,這個池子已經從(wCRES/USDT) 池變為了(FDO/FUSDT) 池,即真幣池成了假幣池。因而在這一輪攻擊中,小A 用同樣的攻擊套路得到的結果無非就是拿著新假幣去換舊假幣……

而這倆假幣還都是小A 自己造的。可能,小A 還是個懷舊的人吧……

咱們關於小A 的故事,也走向了尾聲。或許以太坊(Ethereum) 對於攻擊者而言就是一片黑暗森林,獵手與獵物的身份在這個特殊的競技場也並非一成不變。終要記得,當你在凝視深淵的時候,深淵也在凝視著你。

一級標題

一級標題

0x2. 攻擊分析

為便於理解,下文將以第三次攻擊為例子來進行分析,涉及到的池子為(wCRES/USDT) 池

0x2.1 代碼分析

以下提到的函數都來源於資金池(wCRES/USDT) 的合約代碼,並且在本次攻擊事件中被涉及。

0x2.1.1 getVaultReserve 函數

首先來看getVaultReserve 函數,這個函數的功能很簡單,就是當用戶調用它的時候會返回當前池子裡token pair 的儲量。

0x2.1.2 flashloan 函數

上圖就是本次攻擊中資金池合約的flashloan 函數。可以看到當用戶調用這個函數時,會先根據用戶傳入的baseAmount 和quoteAmount 這兩個參數將相應數量的token 轉入參數assetTo 所代表的地址。到此,就算是完成了閃電貸(flashloan) 的借錢環節。順著代碼往下看,只要data 不為空,就會觸發外部邏輯。而這個外部邏輯,就是調用者自己實現的,本次攻擊的實現,也正是在這個外部邏輯執行時做了手腳。

0x2.1.3 init 函數

上圖就是本次攻擊中最為關鍵的初始化函數init. 可以注意到該函數是外部調用的,而且最關鍵的是,這個函數有以下兩個特點:

  • 【沒有權限要求】這就意味著任何人都可以從外部調用這個函數。

  • 【可被重複調用】則意味著這個init 函數除了在該池子建池時被必要的調用一次後,仍然可以在任何時候被調用來重新初始化這個池子。

調用者調用這個函數時,所傳入的兩個參數baseTokenAddress 和quoteTokenAddress 會被用來給變量_BASE_TOKEN_ 和_QUOTE_TOKEN_ 分別的重新賦值。而這兩個變量的改變就意味著這個池子的token pair 也會發生改變。而這,便是wCRES/USDT池變FDO/FUSDT的原因。

0x2.2 攻擊過程

攻擊交易的流程如下圖所示

[BlockSec DeFi 攻擊系列之五] 以假亂真:DODO V2 眾籌池造襲事件分析

Step 0

  • 攻擊者首先創建了兩個ERC20 代幣的合約(FDO 和FUSDT),可以簡單理解為造了倆假幣(沒價值)。並向自己的地址(0x368a6) 分別轉入了大量的這兩種token,為之後的攻擊做鋪墊。

    [BlockSec DeFi 攻擊系列之五] 以假亂真:DODO V2 眾籌池造襲事件分析

Step 1

  • 攻擊者調用資金池合約的getVaultReserve 函數,獲取到wCRES 和USDT 當前在池子裡的儲量,為下一步攻擊做準備。

Step 2

  • 攻擊者根據Step 1得到的儲量,分別調用兩個假幣合約的transferFrom 函數給資金池合約(DLP)轉入略多於該儲量的假幣(FDO 和FUSDT),從而保證能通過flashloan 函數的餘額檢查。

Step 3

  • 攻擊者接著調用資金池合約(DLP) 的flashloan 函數,借出了略少於資金池儲量的真幣(wCRES 和USDT)。

Step 4

  • flashloan 函數在將wCRES 和USDT 轉入到攻擊者預先設置好的合約地址之後,會自動調用攻擊者的外部邏輯。而攻擊者就是在外部邏輯的實現中調用了wCRES/USDT 這個池子的初始化函數init ,將該池子的token pair 替換為了FDO/FUSDT。

    [BlockSec DeFi 攻擊系列之五] 以假亂真:DODO V2 眾籌池造襲事件分析

Step 5

  • 如下圖,flashloan 函數在執行完外部邏輯之後,會對當前池子的代幣(token) 餘額進行檢查。但需要注意的是,由於池子的token pair 被替換為了FDO/FUSDT,所以baseBalance 和quoteBalance 的所代表的餘額也是FDO 和FUSDT 的餘額。

  • 一級標題Step 2 一級標題

0x3. 總結及安全建議

本次攻擊之所以發生,最主要的問題出在DODO V2 眾籌池的init 函數上。查看該函數的調用情況可以發現,正常的邏輯應該是該函數只能在剛建池的時候被調用一次,之後就應該設置訪問權限,同時也不能被重複調用。攻擊者就是利用了init 函數可以被重複調用來重新初始化池子的這一漏洞,結合閃電貸(flashloan) 將池子中的真幣用假幣套取了出來,從而完成了攻擊。

因此,我們給相關項目方的建議有:

  • 一級標題

  • 一級標題

二級標題

二級標題

文中涉及的外部地址和合約地址

文中涉及的交易

第一次攻擊

第二次攻擊

第三次攻擊

第四次攻擊

第五次攻擊

BlockSec
作者文库