深度解析 Optimism竊取事件:Layer2 網路合約部署重放攻擊

買賣虛擬貨幣
cobo對 optimism竊取事件進行分析與復現,圍繞時間線、攻擊路線、以太坊合約地址生成、技術細節等多重角度進行全面技術解讀。

事件概述

今年五月底 optimism 基金會聘請 做市商 wintermute 為 op 代幣提供流動性,optimism 基金會為 wintermute團隊 提供 2000萬 op 代幣用於做市。此過程中出現了溝通失誤,wintermute團隊 向 optimism 基金會提供了 layer1(eth) 上的收款賬戶,而此賬戶尚未在 layer2(optimism) 上部署,optimism 基金會向 layer2 賬戶打款後,wintermute團隊 發現了該問題,但在賬戶修復之前,攻擊者提前取得了該賬戶許可權,開始拋售賬戶中的op代幣。

時間線

●05.26 & 05.27 -  optimism 基金會向 wintermute團隊 提供的地址 0x4f3a120e72c76c22ae802d129f599bfdbc31cb81 分別打款 1 op 和 100萬 op 作為賬戶測試。

●05.27 - optimism 基金會向該地址打款剩餘 1900萬 op。

●05.30 - wintermute團隊 發現賬戶錯誤,聯絡 optimism 基金會,並聯系 gnosis safe 團隊請求協助取回資金。wintermute團隊 在與 optimism基金會 和 gnosis safe 團隊協商後,評估該賬戶目前仍是安全的,不會被 wintermute團隊 之外的人控制,在 gnosis safe 團隊的幫助下可以取回資金。並計劃在 06.07 日修復賬戶許可權。

●06.01 - 攻擊者部署攻擊合約,合約內硬編碼了 factory 地址,說明此時攻擊者已確定了完整的攻擊流程。

●06.05 - 攻擊者發起攻擊,取得目標賬戶許可權。並轉出 100萬 op 到 tornado 進行兌換。

●06.09 - wintermute團隊 發表宣告對此過失負全責,並會回購所有攻擊者拋售的代幣,同時要求攻擊者歸還剩餘代幣。宣告發表4小時後,攻擊者再次轉出 100萬 op 到某私人賬戶。

攻擊路線

1、所有 gnosis safe 保險箱合約均由 gnosis safe proxy factory 合約部署,要獲得目標地址控制權需要呼叫 proxy factory 在此地址上部署 proxy 合約。

2、攻擊發生前,layer2(optimism) 上的 proxy factory 合約尚未部署,攻擊者透過重放 layer1 上 factory 合約的部署交易,在 layer2 上部署新的 factory 合約。

3、在 layer2 上透過多次呼叫 factory 合約的 createproxy 方法部署 proxy 合約,不斷累加 factory 合約 的 nonce,直到將 proxy 部署到目標地址。

4、在呼叫 createproxy 部署 proxy 時,將 mastercopy 引數設定為攻擊者控制的合約地址,mastercopy 將作為 proxy 的 implementation。至此攻擊者得到目標地址的控制權。

以太坊合約地址生成

為了解釋上述攻擊路線為何可以在特定地址部署合約,我們需要了解一下以太坊的合約地址生成機制。

合約地址不存在對應的私鑰,地址是在合約部署時確定的,部署合約的方法有 create 和 create2 兩種。create 生成地址具體演算法為,發起部署的地址和該地址關聯的 nonce 透過 rlp 進行編碼,編碼後的資料再透過 sha3 得到 hash,取後20個byte作為新部署合約的地址。

new_address = hash(sender, nonce)

具體程式碼可用 js 表示為:

const web3 = require("web3");
const rlp = require("rlp");

const nonce = 0;
const account = "0xa990077c3205cbdf861e17fa532eeb069ce9ff96";

var e = rlp.encode(
    [
      account,
      nonce,
    ],
  );
const noncehash = web3.utils.sha3(buffer.from(e));
console.log(noncehash.substring(26));

對於 eoa 賬戶,每發起一筆交易 nonce 就會 +1,對於合約賬戶,每次建立一個合約則 nonce +1。

create2 一般由智慧合約呼叫,生成地址演算法如下,編碼方式同create:

new_address = hash(0xff, sender, salt, bytecode)

●0xff 是固定的常數

●sender為發起部署的地址

●salt為 sender指定的任意值

●bytecode 為要部署的合約程式碼

●create2 避免了引入遞增的 nonce 。將其替換為sender可控的 salt,這樣可以更好地控制合約部署地址。

技術細節

layer1 proxy 建立過程

由上一節的合約地址生成方式可知,如果要在layer2特定地址部署合約,我們需要首先確定他在layer1 上的部署方式。layer1 上的wintermute多籤賬戶 proxy 由交易 https://etherscan.io/tx/0xd705178d68551a6a6f65ca74363264b32150857a26dd62c27f3f96b8ec69ca01  建立,使用的 factory 為 proxy factory 1.1.1。建立方法為 createproxy。proxyfactory.createproxy 方法實現如下:

function createproxy(address mastercopy, bytes memory data)
    public
    returns (proxy proxy)
{
    proxy = new proxy(mastercopy);
    if (data.length > 0)
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            if eq(call(gas, proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) { revert(0, 0) }
        }
    emit proxycreation(proxy);
}

可以看到此處使用了 new 關鍵字去部署proxy合約,new關鍵字底層呼叫create opcode 執行合約部署,而非 create2 。

因此我們在 layer2 上呼叫相同地址上的 proxy factory 1.1.1 合約的createproxy方法,只要能在部署時達到相同的 nonce ,就能建立出與 layer1 上相同地址的 proxy 合約。

layer2重放部署 gnosis safe proxy factory 1.1.1

在攻擊發生之前,optimism 上尚未部署 gnosis safe proxy factory 1.1.1。因此我們需要先將 proxy factory合約部署到與 layer1 相同的地址上。

layer1  上 proxy factory 1.1.1 地址為 0x76e2cfc1f5fa8f6a5b3fc4c8f4788f0116861f9b ,由 交易 https://etherscan.io/tx/0x75a42f240d229518979199f56cd7c82e4fc1f1a20ad9a4864c635354b4a34261  在 2019 年建立。

解碼該交易 https://www.ethereumdecoder.com/?search=0x75a42f240d229518979199f56cd7c82e4fc1f1a20ad9a4864c635354b4a34261 ,原始資料為:

{
  "nonce": 2,
  "gasprice": {
    "_hex": "0x02540be400"
  },
  "gaslimit": {
    "_hex": "0x114343"
  },
  "to": "0x00",
  "value": {
    "_hex": "0x00"
  },
  "data": "xxxx...xxxx",
  "v": 28,
  "r": "0xc7841dea9284aeb34c2fb783843910adfdc057a37e92011676fddcc33c712926",
  "s": "0x4e59ce12b6a06da8f7ec7c2d734787bd413c284fc3d1be3a70903ebc23945e8c"
}

發現 v = 28, 根據 eip-155 ,v = 28 或者 v = 27 的交易未使用 eip-155 簽名,不會將 chainid 引入到交易簽名中。

因此該 layer1 上的合約部署交易可以直接在 layer2(optimism) 上進行重放,重放交易為 https://optimism.etherscan.io/tx/0x75a42f240d229518979199f56cd7c82e4fc1f1a20ad9a4864c635354b4a34261 ,該交易部署了一個新的 gnosis safe proxy factory,地址與 layer1 上相同 : https://optimism.etherscan.io/address/0x76e2cfc1f5fa8f6a5b3fc4c8f4788f0116861f9b   。

圖中可以看到,該合約4天前攻擊開始時才建立,但是在建立前卻有多次合約呼叫記錄。

此時該factory合約賬戶的 nonce 為 0 ,對於合約賬戶,每次部署新的合約就會使其 nonce + 1 。因此只要一直呼叫 createproxy 方法部署 proxy 合約來累加 nonce,最終就能夠得到部署在目標地址的 proxy 合約。

layer2目標地址部署 proxy

攻擊合約 0xe7145dd6287ae53326347f3a6694fcf2954bcd8a[1]

攻擊者透過該攻擊合約批次呼叫 proxyfactory[2] 的 createproxy 方法,並將 mastercopy 地址引數指定為自身的合約地址,從而大量建立由攻擊合約自身地址作為 implementation 的 proxy 合約。由此實現對目標合約的控制。

具體過程:

攻擊者從eoa地址1[3]多次呼叫攻擊合約,每個交易會建立 162 個 proxy 合約,最終在交易 https://optimism.etherscan.io/tx/0x00a3da68f0f6a69cb067f09c3f7e741a01636cbc27a84c603b468f65271d415b 中建立出了地址為 0x4f3a120e72c76c22ae802d129f599bfdbc31cb81 的目標合約。

攻擊者以eoa地址2[4]作為 proxy owner,控制目標合約操作。

> eth_call 0x4f3a120e72c76c22ae802d129f599bfdbc31cb81 owner()
0x0000000000000000000000008bcfe4f1358e50a1db10025d731c8b3b17f04dbb

經驗教訓

隨著更多以太坊第二層解決方案(如 optimism、arbitrum)和側鏈(如xdai, polygon)的出現,資產和dapp分散在不同的網路中。然而 eoa 賬戶 和 gnosis safe  這樣的智慧合約賬戶在跨鏈的行為上差異巨大。

eoa賬戶本身基於私鑰,可以跨網路使用,但是合約賬戶本身沒有私鑰而且需要複雜的部署和初始化邏輯才能確定控制權和賬戶功能,無法直接跨鏈使用。

同樣的,在不同的網路上,作為基礎設施的合約也並不是憑空出現、對等複製的。

因此在處理不同層級的網路時,對涉及合約的操作需要謹慎,首先需要檢查目標網路和 eth 主網的合約地址、合約內容、合約狀態是否相同,在涉及複雜邏輯的情況下,甚至需要檢查目標網路特性和合約功能是否相容。不能想當然地認為layer1的地址會無差別的對映到 layer2或側鏈中,避免像 wintermute團隊 失竊這樣的烏龍事件再次發生。

參考資料

[1]攻擊合約: https://optimism.etherscan.io/address/0xe7145dd6287ae53326347f3a6694fcf2954bcd8a

[2]proxyfactory: https://optimism.etherscan.io/address/0x76e2cfc1f5fa8f6a5b3fc4c8f4788f0116861f9b#code

[3]攻擊者eoa地址1: https://optimism.etherscan.io/address/0x60b28637879b5a09d21b68040020ffbf7dba5107

[4]攻擊者eoa地址2: https://optimism.etherscan.io/address/0x8bcfe4f1358e50a1db10025d731c8b3b17f04dbb


免責聲明:

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

推荐阅读

;