狸貓換太子:Vee Finance 安全事件再分析

買賣虛擬貨幣

在昨天(2021年9月22日)Vee Finance專案發生攻擊事件之後,我們第一時間對該事件開展分析併發布了初步的分析報告(似曾相識燕歸來:Vee Finance 安全事件分析)。但在分析過程中,依然存在一些懸而未決的疑點:

在攻擊交易中createOrderERC20ToERC20函式呼叫,有一個cToken行為比較奇怪,和這個地址相關的交易只有幾十條;後續查明這個cToken是由攻擊者控制的賬戶建立的。

在重新Review Vee專案的程式碼中,我們發現前一篇文章中發現整個發起槓桿交易的borrowAndCall呼叫並沒有對第一筆交換的價值進行判斷這個分析是不確切的。下文會闡述,在整個呼叫過程中存在對交換前後的價值進行判斷的程式碼邏輯。

在分析攻擊交易的Trace中,我們發現了一個奇怪的BTC代幣地址,該地址和前一篇文章所述的攻擊過程並無關聯。

在我們的分析報告發布之後,專案方也公佈了自己的官方分析報告(The Main Cause of Vee Finance Attack: https://veefi.medium.com/the-main-cause-of-vee-finance-attack-7a8475085ec5),然而該報告依然無法解釋上述疑問。鑑於此,我們對Vee專案和此次攻擊事件進行了更為細緻的分析和覆盤。分析結果表明,導致此次攻擊的根本原因是校驗機制的缺陷,而非如官方分析報告宣稱的,諸如單一價格預言機等因素帶來的影響。

0x1. 深入分析Vee專案程式碼

在後續對Vee專案方的程式碼進行深入分析的過程中,我們發現上述的檢驗過程其實是存在的,只是在攻擊者巧妙利用下檢查被繞過。下面我們來分析如何檢查和攻擊者如何繞過的過程:

首先在createOrderERC20ToERC20函式中,紅色箭頭標註的getAmountOutMin呼叫會對交換前後的價值進行檢查。當然,我們首先注意到在整個函式呼叫中,並沒有對cToken的真實性做檢查。也就是說,攻擊者可以自己建立一個cToken並呼叫createOrderERC20ToERC20函式建立一個訂單。這為攻擊者的攻擊埋下了伏筆。

在getAmountOutMin函式中,對第一次交換前後的價值是這樣做判斷的:

首先獲得傳入和傳出的cToken(ctokenA和ctokenB),從PriceOracle中呼叫getUnderlyingPrice獲得其Underlying代幣的價格。

計算呼叫calcSwapAmount,扣除交易費用,計算真正的swapAmountA = amountA * leverage * (1 - serviceFee)。

計算由Oracle返回的應該轉出的tokenA的估計量,即amountFromOracle = (priceA * swapAmountA) / priceB。

呼叫getAmountOut,返回從Pangolin交易所真正返回的轉出tokenA的數量amountOut。

對比Oracle返回的tokenA轉出估計量amountFromOracle和具體數量amountOut。如果amountFromOracle * 0.95 > amountOut,代表真正交易獲得的tokenB過少,則需要拒絕這筆交易。

第二次Review整個邏輯實現,我們注意到這裡有幾次呼叫cToken的underlying()函式的過程:

第一次在createOrderERC20ToERC20函式中,獲得了tokenA、tokenB,後續函式中幾乎所有需要用到Underlying的地方傳入的都是這兩個Token。

第二次在createOrderERC20ToERC20的getAmountOutMin呼叫中。如上文所述,這個呼叫的主要目的是檢驗此次Swap前後的價值是否一致,專案方本身是否受損。

那麼在getAmountOutMin中是怎麼檢驗的呢?我們重述一下這個過程:

重新呼叫underlying()函式獲得tokenA和tokenB。

從PriceOracle獲得ctokenA和ctokenB對應的Underlying價格。在這個過程中會再次呼叫underlying()獲得cToken對應的Underlying。

用第一步獲得的tokenA和tokenB,去Pangolin查詢能換得的tokenB數量,並與PriceOracle的數量進行比較。

一般來說這個過程是沒有問題的,這是由於正常的cToken合約,其Underlying是固定的。但是在整個過程中沒有對cToken是否真實進行驗證,這導致攻擊者可以傳入自己設定的cToken合約。

而攻擊者又是如何巧妙運用這個不一致性的呢?

在createOrderERC20ToERC20呼叫開頭,讓underlying()函式返回LINK代幣。因此後續真正執行的交易是WETH兌換LINK。

在getAmountOutMin函式中,讓underlying()函式返回BTC代幣,使這一步兌換價值校驗能夠透過。

更為巧妙的是,由於swapERC20ToERC20的第四個引數(如下圖所示)依賴getAmountOutMin函式返回的結果,因此攻擊者選取了BTC這個價值較高的代幣,使得合約對交換獲得的代幣數量的下限要求很低。

校驗完成後,真正執行的交易是在攻擊者建立的不平衡交易對中,將WETH兌換為LINK的交易,成功用少量的LINK套出了Vee合約的WETH。

透過巧妙地設計了underlying()函式,攻擊者成功地“狸貓換太子”,將(Vee合約認為的)BTC替換成了LINK。再根據之前所述的過程將Vee合約中鎖倉的流動性套出,完成了此次攻擊。

當然,專案方還是做了許多檢查。在createOrderERC20ToERC20中,合約呼叫了一個檢查函式進行檢查,其中對轉出Token做了檢查:

也就是說,每個槓桿交易的第一步交易,轉入的Token(也就是代理合約使用槓桿借入的資金換得的Token)必須是在專案方自己控制的白名單內的。

總結來說,專案方的兩個疏忽導致了此次攻擊:

沒有對使用者建立訂單時傳入的cToken進行驗證。任何人都可以建立一個cToken,然後建立這個cToken對應的訂單。

沒有在Pangolin建立專案支援Token的交易對,或者說沒有維持交易對的流動性。只有維持了交易對的流動性,槓桿交易的第一次交換才能換到等值的代幣。

0x2. 關於官方分析報告

在攻擊發生不久後,Vee專案官方釋出了分析攻擊原因的報告(https://veefi.medium.com/the-main-cause-of-vee-finance-attack-7a8475085ec5)。其中專案方認為導致攻擊的原因有以下幾個:

價格預言機只有一個價格來源,因此這個價格受到了市場波動的影響。

價格處理時沒有考慮不同Token的decimals可能不同。

交易對在交易時沒有設立白名單機制。

首先,Vee專案的價格預言機並沒有開源。但由於Vee是一個借鑑Compound的專案,而Compound的預言機是開源的,從原始碼中可以看出:

Compound的預言機在計算Underlying價格時是考慮了不同Token的decimals可能不同這種情況的。除非Vee專案對Compound預言機進行了大幅修改,否則預言機不太可能是導致此次攻擊的罪魁禍首。事實上,攻擊交易中預言機返回的報價如下圖所示:

注意這裡的16進位制值0x15e1549d1216fe9fc032e7c00000對應的十進位制值為443783124870000000000000000000000,正好是當時BTC的價格,可以作為預言機清白的旁證。

同樣在之前的分析中可以看出,Vee是對槓桿交易能夠換到什麼代幣做了嚴格的白名單檢查的。因此白名單檢查也不能為此次攻擊背鍋。

綜上所述,真正導致攻擊的問題在於校驗機制的缺陷:建立訂單的cTokenB(槓桿交易第一筆交易要兌換獲得的Token),其地址是使用者(透過order引數)可以完全控制的;而直到訂單執行的整個過程中,該地址都是被直接使用的,並未經過任何校驗。

0x3. 結語

本次攻擊手法隱蔽而巧妙,整個分析過程也是百轉千折。當然,在這一過程中我們也有很多收穫。安全之路上,“博學之,審問之,慎思之,明辨之,篤行之”,誠哉斯言!

免責聲明:

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

推荐阅读