慢霧:詳解 Uniswap 的 ERC777 重入風險

買賣虛擬貨幣
前言據鏈聞訊息,4 月 18 日,Tokenlon 宣佈暫停 imBTC 轉賬,因其發現有攻擊者透過 ERC777 在 Uniswap 流動性合約中的重入漏洞,對 ETH-imBTC 池迴圈套利。此次的攻擊手法是一個存在於 Uniswap v1 上的已知漏洞,該漏洞最早由 Consensys 於 2019 年 4 月發現,當時 Consensys 只是發現了該風險,還沒有發現可以利用這種手法進行攻擊的 token。隨後,在 imBTC 上線 Uniswap 後,由於 imBTC 是基於 ERC777 實現的,透過組合 ERC777 的特性及 Uniswap 程式碼上的問題,使攻擊者可以透過重入漏洞實現套利。下面,我們將來分析此次套利中的攻擊手法和具體的細節。知識準備ERC777 協議是以太坊上的代幣標準協議,該協議是以太坊上 ERC20 協議的改進版,主要的改進點如下:1、使用和傳送以太相同的理念傳送 token,方法為:send(dest, value, data)2、合約和普通地址都可以透過註冊 tokensToSend hook 函式來控制和拒絕傳送哪些token(拒絕傳送透過在hook函式tokensToSend 裡 revert 來實現)
3、合約和普通地址都可以透過註冊 tokensReceived hook 函式來控制和拒絕接受哪些token(拒絕接受透過在hook函式tokensReceived 裡 revert 來實現)4、tokensReceived 可以透過 hook 函式可以做到在一個交易裡完成傳送代幣和通知合約接受代幣,而不像 ERC20 必須透過兩次呼叫(approve/transferFrom)來完成5、持有者可以"授權"和"撤銷"操作員(operators: 可以代表持有者傳送代幣) 這些操作員通常是(去中心化)交易所、支票處理機或自動支付系統6、每個代幣交易都包含 data 和 operatorData 欄位, 可以分別傳遞來自持有者和操作員的資料7、可以透過部署實現 tokensReceived 的代理合約來相容沒有實現tokensReceived 函式的地址在這裡,我們需要特別關注的點是第二點,即 ERC777 標準中的 tokenToSend 函式,根據 ERC777 協議的定義,遵循該標準的 token 代幣在每一次發生代幣轉賬的時候都會去嘗試呼叫代幣傳送者 tokensToSend 函式,而代幣持有者可以透過在 ERC1820 註冊合約註冊自己的合約並透過在這個 hook 函式中定義一些操作來處理代幣轉賬的過程中的某些流程,如拒絕代幣傳送或其他操作。
瞭解這些關鍵點,有助於我們理解這次攻擊的具體攻擊手法。現在開始,我們可以稍微加速,看看對於 Uniswap 而言,這次到底發生了什麼?細節分析透過 Etherscan 查詢攻擊者的其中一筆交易 0x32c83905db61047834f29385ff8ce8cb6f3d24f97e24e6101d8301619efee96e

可以發現,攻擊者兩度向 Uniswap 合約轉帳 imBTC,金額同樣是 0.00823084,然後從 Uniswap 收取了兩筆 ETH,看上去似乎是十分正常的兩筆交易,實際上卻是暗流湧動,另有玄機。為了更好的瞭解整一筆交易的細節,我們需要透過 bloxy.info 來檢視交易的具體細節。

透過查詢交易的細節,我們發現,攻擊者首先是透過 ethToTokenSwapInput 函式向 Uniswap 兌換了一些 imBTC,然後再透過 tokenToEthSwapInput 函式開始第一次用 imBTC 換取 ETH,然後 Uniswap 先將 ETH 轉給了攻擊者,再呼叫 imBTC 的 transferFrom 函式,由於 imBTC 實現了 ERC777 標準,所以在呼叫 imBTC 的 trasferFrom 函式的時候, imBTC 會對攻擊者的 tokensToSend 函式進行呼叫。隨後,在攻擊者的 tokensToSend 函式中,攻擊者會進行第二次用 imBTC 換取 ETH,然後流程結束。

從交易細節上看,這裡似乎還是沒有什麼問題,我們繼續跟蹤 UniSwap 的程式碼。

上面是程式碼是 Uniswap 的 ethToTokenSwapInput 函式的程式碼,根據程式碼分析, Uniswap 的 ethToTokenSwapInput 函式會呼叫 ethToTokenInput 函式,然後會先透過 getInputPrice 獲取代幣能換取的 eth 數量,之後透過 send 函式將 eth 發給使用者,最後再透過 transferFrom 把代幣轉進合約。我們繼續跟進 getInputPrice 函式。

透過分析 getInputPrice 函式,我們能知道,ETH 獲取量計算的公式為

把該公式放到 ethToTokenInput 函式的上下文中,該公式就變成了

在該公式下,一次正常的 imBTC 兌換 ETH 的過程中,作為分母的 imBTC 儲備量在兌換過後應該要上升,對應的 ETH 儲備量會變小。

但是回顧攻擊者的操作方式,在攻擊者第一次傳送 imBTC 兌換 ETH 的過程中,Uniswap 會先傳送 ETH 給攻擊者,這時候 Uniswap 中 ETH 儲備量減少,然後 Uniswap 呼叫 transferFrom 函式,(注意此時還未將攻擊者的 imBTC 扣除), 緊接著在 transferFrom 函式中攻擊者呼叫的第二次的 ethToTokenSwapInput 時,透過 getInputPrice 獲取兌換的 ETH 數量的公式會變成這樣:

注意看,在第二次的兌換計算中,只有 ETH 的儲備量變少了,而 imBTC 的儲備量並未增加,這導致相比與單獨的呼叫 ethToTokenSwapInput 函式,攻擊者可以透過重入的方式,在第二次使用 imBTC 兌換 ETH 的過程中,使計算公式的分子發生了變化,而公式的分母不會發生變化。相比正常的兌換,攻擊者透過重入方式進行的第二次兌換會獲取微小的利潤,導致有利可圖。重複這樣的過程,就能透過等量的 imBTC 獲取更多的 ETH,導致 Uniswap 做事商的損失。

防禦方法

1. 在 Uniswap 的 tokenToEthSwapInput 函式中加入 OpenZeppelin 的 ReentrancyGuard 函式,防止重入問題。
2. 在進行代幣交換的時候,先扣除使用者的代幣,再將 ETH 傳送給使用者。

同時,針對本次攻擊事件慢霧安全團隊建議:

1. 在關鍵的業務操作方法中加入鎖機制,如:OpenZeppelin 的 ReentrancyGuard
2. 開發合約的時候採用先更改本合約的變數,再進行外部呼叫的編寫風格
3. 專案上線前請優秀的第三方安全團隊進行全面的安全審計,儘可能的發現潛在的安全問題
4. 多個合約進行對接的時候也需要對多方合約進行程式碼安全和業務安全的把關,全面考慮各種業務場景相結合下的安全問題
5. 合約儘可能的設定暫停開關,在出現“黑天鵝”事件的時候能夠及時發現並止損
6. 安全是動態的,各個專案方也需要及時捕獲可能與自身專案相關的威脅情報,及時排查潛在的安全風險

最後的思考

這兩天的 DeFi 世界被鬧得沸沸揚揚,imBTC 作為 ERC777 代幣首當其衝,ERC777 協議也飽受詬病,但是看完分析,造成此次的攻擊事件原因,真的是 imBTC 或者是 ERC777 協議的問題嗎?

如果 Uniswap 做好了 ERC777 的相容,使用 ReentrancyGuard,並在代幣交換的時候先扣除使用者的代幣,再將 ETH 傳送給使用者,這樣的問題是不是就不會發生?

imBTC 作為 以太坊上 token 化的比特幣代幣協議,其安全性在自身單獨執行的時候並不存在問題,第三方 DeFi 平臺在接入的時候,應需要充分考慮平臺本身的業務邏輯與接入代幣之間的相容性,才能避免因相容性發生不必要的安全問題。而不是簡單的將問題歸咎於協議和代幣提供方。

免責聲明:

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

推荐阅读

;