例如:小王認為行情進入了下跌趨勢,看到 Opyn 上掛著一個小李對 ETH 330美元的看跌期權,於是進入交易系統,向小李轉賬一個 ETH,獲得小李抵押的等額數字資產。若此刻行情已經跌至了300美元,小王便可獲得其中的差價。
如上面的合約程式碼片段所示,行權函式 exercise() 的內部是一個迴圈,依據引數中傳遞的 vaultsToExerciseFrom 中的地址數量依次呼叫真正的行權邏輯 _exercise() 函式。
函式處理 ERC20 Token 時,和大部分的 DeFi 專案做法一樣,使用 transferFrom(),如程式碼 1882 行所示,從 msg.sender 轉賬到 address(this)。
但是當函式處理的資產為 ETH 時,處理的方式就完全不一樣了。因為在 Solidity 中,msg.value 的意思是合約呼叫者在呼叫具有 payable 介面時所轉給該合約的 ETH 數量,僅是一個量值,所以在合約程式碼的 1879 行中,檢查 msg.value == amtUnderlyingToPay 僅能確保合約確實收到了 amtUnderlyingToPay 數量的 ETH,並不會對 msg.value 的值造成任何影響。
但是正如上面講到的在 exercise() 中會迴圈呼叫 _exercise() 函式,這導致儘管合約實際只收到一次ETH,然而在迴圈過程中卻可以重複使用。
攻擊點就在這裡,由於合約少了一步對 ETH 實時數量的檢驗,使得攻擊者可以先偽造一筆指向自己的交易,然後再把已經花掉的本金再次利用,和平臺其他使用者完成一筆正常交易。
在圖3中,我們透過 Bloxy 瀏覽器顯示的呼叫過程來展示攻擊的過程。由於攻擊者吃掉了很多筆訂單,我們以其中一筆交易為例,向大家展示其攻擊邏輯:
1、攻擊者先從 Uniswap 購入了 75 oETH 為進一步呼叫函式行權做好籌備;
2、攻擊者建立了一個 Vault 地址,作為看空期權賣方,並且抵押24,750 USDC 鑄造出75 oETH,但並未賣出這些期權,等於自己同時買入了以 330 的價格賣出75 ETH 的權利;
3、攻擊者在 Opyn 合約中呼叫了 exercise(),在持有 150 oETH 看空期權的情況下,先向自己的 Vault 地址轉入了75個 ETH,獲得自己事先抵押的 24,750 個 USDC,再重利用了這75個 ETH,成功吃掉了另一個使用者的 24,750 個 USDC,進而實現非法獲利。
修復建議
PeckShield 安全團隊建議,在 Solidity 中,合約可使用一個區域性變數 msgValue 來儲存所收到 ETH(即 msg.value 的值)。這樣,在後續的步驟中透過操作 msgValue ,就能準確的標記有多少 ETH 已經被花費,進而避免資產被重複利用。此外,我們還可以使用 address(this).balance 來檢查合約餘額來規避 msg.value 被重複使用的風險。
PeckShield 作為業內領先的區塊鏈安全公司,安全業務已覆蓋全球範圍,主要客戶包括有:公鏈提供商 (EOS、Nervos、Harmony、AVA、HBTC、NEO 、IOST、Bytom、TRON、OKChain),頭部錢包和礦池 (imToken、SparkPool、位元派、Cobo 金庫,VoiceWallet),以及頭部交易所(Huobi、KuCoin、Bithumb、Upbit、OKex)、DeFi 應用及智慧合約(MakerDAO、StarkWare、bZx、Aave、Tokenlon、InstaDApp、Set Protocol、Zerion、Ren Project、Hydro Protocol、dForce、Newdex、HoneyLemon)等。
近一年內,PeckShield 已經接連審計了數十個 DeFi 專案,幫助 DeFi 協議做程式碼安全審計、業務邏輯風控、威脅情報風險預警等等,已經成為服務 DeFi 領域的頭部安全公司。