作者:九九& Lisa

編輯:Sherry

背景

2025年3 月30 日,根據慢霧MistEye 安全監控系統監測,Ethereum 鏈上的槓桿交易項目SIR.trading (@leveragesir) 遭攻擊,損失價值超30 萬美元的資產。慢霧安全團隊對此事件展開分析,並將結果分享如下:

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

 (https://x.com/SlowMist_Team/status/1906245980770746449)

相關資訊

攻擊者地址:

https://etherscan.io/address/0x27defcfa6498f957918f407ed8a58eba2884768c

存在漏洞的合約地址:

https://etherscan.io/address/0xb91ae2c8365fd45030aba84a4666c4db074e53e7#code

攻擊交易:

https://etherscan.io/tx/0xa05f047ddfdad9126624c4496b5d4a59f961ee7c091e7b4e38cee86f1335736f

前置知識

Solidity 0.8.24 版本(2024 年1 月發布)引入了基於EIP-1153 的瞬態儲存(transient storage) 特性。這是一種新的資料儲存位置,旨在為開發者提供一種低成本、交易期間有效的臨時儲存方式。

瞬態儲存是一種與儲存(storage)、記憶體(memory) 和呼叫資料(calldata) 並列的新資料位置。其核心特點是資料僅在目前交易執行期間有效,交易結束後會自動清除。存取和修改瞬態儲存透過兩個新的EVM 指令實現:

  • TSTORE(key, value):將256 位元的值value儲存到瞬態儲存的指定鍵key對應的記憶體中。
  • TLOAD(key):從瞬態儲存的指定鍵key對應的記憶體中讀取256 位元的值。

此特性主要有以下特點:

  • 低gas 成本:TSTORE 和TLOAD 的gas 成本固定為100,相當於熱儲存存取(warm storage access)。相較之下,常規儲存作業(SSTORE) 在首次寫入(從0 到非0)時可能高達20,000 gas,更新時也至少需要5,000 gas。
  • 交易內持久性:瞬態儲存的資料在整個交易期間保持有效,包括所有函數調用和子調用,適合需要跨調用共享臨時狀態的場景。
  • 自動清除:交易結束後,瞬態儲存自動重設為零,無需手動清理,減少了開發者維護成本。

根本原因

這次被駭事件的根本原因是,在函數中呼叫tstore 進行瞬態儲存的值在函數呼叫結束後並沒有被清空,導致攻擊者可以利用這個特性建構特定的惡意位址來繞過權限來檢查轉出代幣。

攻擊步驟

1. 攻擊者首先創建兩個惡意代幣A 和B,之後在UniswapV3 上為這兩個代幣創建池子並注入流動性,其中A 代幣為攻擊合約。

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

2. 接著攻擊者調用Vault 合約的initialize 函數,以A 代幣為抵押品代幣,B 代幣為債務代幣創建一個槓桿交易市場APE-21。

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

3. 緊跟著攻擊者調用Vault 合約的mint 函數,存入債務代幣B 鑄造槓桿代幣APE。

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

跟進到mint 函數中,我們發現當需要存入債務代幣B 去鑄造槓桿代幣時,需要傳入的collat​​eralToDepositMin 參數的值不能等於0,之後會透過UniswapV3 先將B 代幣兌換成抵押品代幣A 並轉入Vault 中,其中會將攻擊者先前創建的UniswapV3 的瞬態存儲。

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

當UniswapV3 池子進行兌換操作時,會回呼Vault 合約的uniswapV3SwapCallback函數。可以看到:此函數首先會用tload 從先前瞬態儲存的指定鍵1 對應的記憶體中取出值,來驗證呼叫者是否是UniswapV3 池子,接著從鑄造者地址轉出債務代幣B 並鑄造槓桿代幣APE,最後將鑄造的數量amount 進行第二次瞬態存儲,保存在指定鍵1 的內存中,返回值對應的函數。這裡需要鑄造的數量是攻擊者事先計算控制好的,其值為95759995883742311247042417521410689。

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

4. 攻擊者之後呼叫Keyless CREATE2 Factory 合約的safeCreate2 函數來建立一個惡意的合約,其合約位址0x00000000001271551295307acc16ba1e7e0d4281,與第二次瞬態儲存的值相同。

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

5. 接著攻擊者透過該惡意合約去直接呼叫Vault 合約的uniswapV3SwapCallback函數轉出代幣。

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

因為uniswapV3SwapCallback函數是透過tload(1) 來取得驗證呼叫者是否為UniswapV3 池子,然而在先前的鑄造操作中,指定鍵1 對應記憶體中的值被儲存為鑄造的數量9575999588374231124704241751 ool 的位址被取得為0x00000000001271551295307acc16ba1e7e0d4281,導致對呼叫者的身分檢查被錯誤地通過。

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

並且攻擊者提前計算好了需要轉出的代幣數量,將最終鑄造的數量amount 構造為指定的值:1337821702718000008706643092967756684847623606640。同樣的,在這次呼叫uniswapV3SwapCallback函數的最後,會進行第三次的瞬態存儲,將該值儲存到指定鍵1 對應記憶體中。這是需要讓該值與攻擊合約(A 代幣)的地址的值0xea55fffae1937e47eba2d854ab7bd29a9cc29170 相同,才能讓之後對呼叫者的檢查通過。

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

6. 最後,攻擊者就可以直接透過攻擊合約(A代幣)去調用Vault 合約的uniswapV3SwapCallback函數,將Vault 合約中的其他代幣(WBTC、WETH) 轉出獲利。

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

MistTrack 分析

根據鏈上反洗錢與追蹤工具MistTrack 的分析,攻擊者(0x27defcfa6498f957918f407ed8a58eba2884768c) 盜取了約30 萬美元的資產,包括17,814.8626 USDC, WE1.4085 WB819.185 1859.

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

其中WBTC 被兌換為63.5596 WETH,USDC 兌換為9.7122 WETH:

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

接著,共193.1428 WETH 轉入Railgun:

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

此外,攻擊者的初始資金來自Railgun 轉入的0.3 ETH:

致命殘留:一場由瞬態儲存引發的30萬美元鏈上劫案

總結

本次攻擊的核心在於攻擊者利用專案中瞬態儲存不會在函數呼叫後將保存的值立即清空,而是在整個交易期間中一直保存的特性,從而繞過了回呼函數的權限驗證來獲利。慢霧安全團隊建議專案方應該根據對應的業務邏輯在函數呼叫結束後立即使用tstore(key, 0) 將瞬態儲存中的值進行清除。此外,應對專案的合約代碼加強審計與安全測試,從而避免類似情況的發生。