返回部落格

第10號全景事件:XOR線性破壞位置指紋方案

Code Auditing
2026年2月13日
閱讀約 7 分鐘

2025 年 8 月 25 日,在 Cantina 和 Seal911 的協助下,Panoptic 進行了白帽救援行動,保護了約 40 萬美元的風險資金 [1]。根本原因是 s_positionsHash 建構方式存在缺陷:協議使用異或(XOR)運算將倉位 ID 的 Keccak256 哈希值聚合為單一指紋。雖然單個 Keccak256 哈希值仍具備抗碰撞性,但 XOR 的數學線性使得複合指紋變得不安全。攻擊者可以產生一組偽造的倉位 ID,使其 XOR 聚合後的哈希值與任何目標指紋相匹配,從而繞過協議的倉位驗證並在不償還債務的情況下提取抵押品。

背景

Panoptic 是一個建立在以太坊上的去中心化永續期權交易協議,允許用戶交易看跌和看漲期權。

withdraw() 函數有一個名為 positionList 的輸入參數,由一組 tokenId 組成。每個 tokenId 代表一個倉位。withdraw() 函數會根據 s_positionsHash 檢查每個倉位的債務狀態,然後提取用戶的抵押品。

為了節省 Gas,協議不會儲存用戶的每一個倉位(即 tokenId)。相反,它根據用戶的所有 tokenId 計算出一個指紋,並將其記錄在 s_positionsHash 中。每個 s_positionsHash 的高 8 位代表 numLegs,而低 248 位代表 user position hash。

對於每個傳入的 tokenId,協議會計算其哈希值,取低 248 位,並透過將其與當前 s_positionsHash 的低 248 位進行按位異或運算來更新 user position hash。

同時,countLegs 會根據 tokenId 的數值範圍進行調整:當 tokenId 小於 2642^{64} 時保持不變;當處於 (264,2112)(2^{64}, 2^{112})、(2112,2168)(2^{112}, 2^{168}) 和 (2168,2208)(2^{168}, 2^{208}) 範圍內時,分別增加 1、2 或 3。最終將此結果寫入 s_positionsHash 的高 8 位以更新 numLegs。

漏洞分析

根本原因在於合約建構 s_positionsHash 的演算法存在缺陷,具體表現為使用 XOR 運算來聚合 Keccak256 哈希結果。雖然單個哈希函數仍然安全,但 XOR 運算的數學線性使得整體的指紋演算法(即哈希值的 XOR 和)變得不安全 [2]。

線性意味著攻擊者不需要破解哈希函數本身(即從哈希中反推 tokenId)。相反,他們可以採用組合策略:產生大量隨機的 tokenId,計算其 Keccak256(tokenId),並從這些哈希值中選擇一個特定的子集,使得這些 tokenId 哈希值的 XOR 和精確匹配受害者的目標指紋。

這使得攻擊者可以在呼叫 withdraw() 時傳入一組偽造的 tokenId,從而通過健康檢查並提取 s_positionsHash 對應的所有抵押品。

理論

假設用戶的倉位指紋 s_positionsHash 的 user position hash 為 TT,numLegs 為 kk,用戶的 tokenId 為 {t1,t2,…,tn}\{t_1, t_2, \dots, t_n\}。因此:

⨁i=1k[Hash(ti)(mod2248)]=T\bigoplus_{i=1}^{k} [\text{Hash}(t_i) \pmod{2^{248}}] = T

這裡,每個 248 位的哈希值可以被視為一個 248 維的向量。

Hash(ti)(mod2248)=[b0,b1,…,b247]T,其中 bi∈{0,1}\text{Hash}(t_i) \pmod{2^{248}} = [b_0, b_1, \dots, b_{247}]^T, 其中\ b_i \in \{0, 1\}

因此,TT 存在於由 0 和 1 組成的 248 維空間(F2248\mathbb{F}_2^{248})中。在這個空間中,XOR 運算等同於向量加法(附錄 I)。

攻擊者的目標是從所有可用向量中找出 nn 個 248 維向量 {v1,v2,…,vn}\{v_1, v_2, \dots, v_n\},使得它們的 XOR 和的低 248 位等於 TT。因此,我們可以將攻擊者的目標表述為一個線性方程組:

x1v1+x2v2+⋯+xnvn=Tx_1 v_1 + x_2 v_2 + \dots + x_n v_n = T

具體來說,我們不需要嘗試直接建構 TT。相反,我們選擇 nn 個線性無關的哈希向量 {v1,v2,…,vn}\{v_1, v_2, \dots, v_n\}(其中 nn 等於維度 248),並將它們作為列向量來建構一個 n×nn \times n 矩陣 AA:

A=[v1,v2,…,vn]A = [v_1, v_2, \dots, v_n]

問題隨即轉換為尋找係數向量 xx,使得:

A⋅x=TA \cdot x = T

其中 x=[x1,x2,…,xn]Tx = [x_1, x_2, \dots, x_n]^T,且 xi∈{0,1}x_i \in \{0, 1\}。

根據線性代數理論,只要矩陣 AA 滿秩,它就生成了整個 nn 維空間。這意味著對於任何目標 TT,該方程組都有唯一解。解出 xx 後,我們只需保留那些 xi=1x_i=1 對應的向量 viv_i,它們的 XOR 和即為 TT。

範例

為了便於理解,我們透過一個案例研究來演示此建構過程。假設向量是 3 維的,每個維度僅由 0 或 1 組成,且目標哈希值為 101,即 T=[1,0,1]TT = [1, 0, 1]^T。

接下來,我們隨機產生 3 個哈希值:

  • v1=[1,1,0]Tv_1 = [1, 1, 0]^T
  • v2=[0,1,0]Tv_2 = [0, 1, 0]^T
  • v3=[0,1,1]Tv_3 = [0, 1, 1]^T

我們使用這三個向量作為列建構矩陣 AA,並建立方程 Ax=TAx=T:

[100111001][x1x2x3]=[101]\begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 1 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix} = \begin{bmatrix} 1 \\ 0 \\ 1 \end{bmatrix}

透過高斯消去法求解,我們得到 x=[1,0,1]Tx = [1, 0, 1]^T。這意味著我們需要選擇 v1v_1 和 v3v_3(對應於 x1=1,x3=1x_1=1, x_3=1),它們的 XOR 和正好等於目標 TT。

選擇 n 個線性無關向量

如前所述,要解出 Ax=TAx=T,我們需要建構一個滿秩矩陣 AA。鑑於我們處理的是一個極大的向量空間(m=2248m = 2^{248}),核心問題是:如何從這個巨大的空間中快速選擇 nn 個線性無關的向量?

考慮最簡單的方法:隨機產生 nn 個 tokenId 並選擇它們的哈希結果作為向量。

在 F2\mathbb{F}_2 上,nn 個隨機選擇的 nn 維向量形成滿秩矩陣的機率 PP 為:

P(n)=∏k=0n−1(1−2k2n)P(n) = \prod_{k=0}^{n-1} \left(1 - \frac{2^k}{2^n}\right)

當 nn 很大時(在本例中 n=248n=248),該機率收斂為一個常數(附錄 II):

lim⁡n→∞P(n)≈0.28879\lim_{n \to \infty} P(n) \approx 0.28879

這意味著每次隨機嘗試都有大約 28.9% 的成功機率。平均而言,攻擊者只需要約 3.5 次嘗試即可找到一組線性無關向量。因此,計算成本極低,攻擊者可以快速建構滿足條件的矩陣 AA。

為了控制高 8 位的 countLegs,我們只需要根據目標 countLegs 調整隨機產生的 tokenId 值的範圍即可。

例如,如果我們希望偽造指紋中的 numLegs 為 0,我們只需確保 nn 個隨機產生的 tokenId 全部小於 2642^{64}。由於此範圍內 tokenId 的 leg 增量為 0,無論高斯消去法的解 xx 選擇了哪些向量,最終累積的 numLegs 都必然為 0。

攻擊分析

白帽救援人員發起了多次救援交易。為簡潔起見,以下討論僅基於其中一筆交易 [3]。

核心邏輯包含以下 5 個步驟:

  1. 透過 Aave 閃電貸借入 0.23e8 WBTC 和 28e18 WETH。
  2. 將 0.23e8 WBTC 和 28e18 WETH 存入 poWBTC 合約和 0x1f8d_poWETH 合約。
  3. 使用正常的 positionIdList 呼叫 PanopticPool 合約的 mintOptions(),以借入資金並開立槓桿倉位。
  4. 呼叫 withdraw(),並傳入偽造的 tokenId。由於這些偽造倉位的 positionSize 為 0,該函數返回的 tokenRequired 為 0,這意味著所有倉位所需的總抵押品被錯誤地計算為零。同時,由這些 tokenId 產生的 s_positionsHash 與步驟 3 中產生的完全相同,從而允許救援人員在不償還任何債務的情況下提取所有抵押品。
  5. 償還閃電貸並執行下一輪。

總結

此次事件突顯了兩個共同作用的缺陷,它們共同實現了未經授權的抵押品提取。

  • XOR 對於哈希而言並非安全的聚合函數。 Keccak256 具有抗碰撞性,但 XOR 的線性意味著多個哈希值的 XOR 和並不繼承該屬性。建構一組其哈希值 XOR 為任何目標值的輸入,簡化為在 F2\mathbb{F}_2 上求解線性方程組,這在計算上是微不足道的。哈希複合需要能夠保持抗碰撞性的運算,例如連接後再進行哈希處理。
  • 缺少倉位 ID 的驗證。 協議未驗證傳入的 positionId 是否對應於有效的期權倉位。小於 2642^{64} 的值具有 0 的 countLegs 和 0 的 positionSize,這意味著偽造的倉位不會產生債務。這允許攻擊者在抵押品要求為零的情況下透過健康檢查,同時匹配目標指紋。

參考資料

  1. https://x.com/Panoptic_xyz/status/1961187739866644524

  2. https://cseweb.ucsd.edu/~mihir/papers/inchash.pdf

  3. https://app.blocksec.com/explorer/tx/eth/0x67a45dfe5ff4b190058674d7c791bbdc48e889f319f937c24fa13a5f9093f088

附錄

以下兩個附錄提供了對正文中陳述的數學解釋和證明,即 XOR 運算等同於向量加法(附錄 I)以及隨機矩陣滿秩的機率(附錄 II)。

附錄 I

在有限域 F2={0,1}\mathbb{F}_2 = \{0, 1\} 中,加法定義為模 2 加法:

0+0=00+1=11+0=11+1=0(mod2)\begin{aligned} 0 + 0 &= 0 \\ 0 + 1 &= 1 \\ 1 + 0 &= 1 \\ 1 + 1 &= 0 \pmod 2 \end{aligned}

觀察發現這與邏輯運算「異或」(XOR,符號為 ⊕\oplus)完全相同。

對於 nn 維向量空間 F2n\mathbb{F}_2^n(本例中 n=248n=248),兩個向量 u=[u1,…,un]Tu = [u_1, \dots, u_n]^T 和 v=[v1,…,vn]Tv = [v_1, \dots, v_n]^T 的加法定義為分量模 2 加法:

u+v=[u1+v1(mod2)⋮un+vn(mod2)]=[u1⊕v1⋮un⊕vn]=u⊕vu + v = \begin{bmatrix} u_1 + v_1 \pmod 2 \\ \vdots \\ u_n + v_n \pmod 2 \end{bmatrix} = \begin{bmatrix} u_1 \oplus v_1 \\ \vdots \\ u_n \oplus v_n \end{bmatrix} = u \oplus v

因此,在 F2n\mathbb{F}_2^n 向量空間中,向量加法等同於向量分量上的按位異或運算。

附錄 II

要使矩陣滿秩,這 nn 個向量必須線性無關。

第一個向量 v1v_1 可以是 F2n\mathbb{F}_2^n 中除零向量外的任何向量,提供 2n−12^n - 1 種選擇;第二個向量 v2v_2 不能位於 {v1}\{v_1\} 生成的子空間中(該子空間包含 212^1 個向量),因此有 2n−22^n - 2 種選擇;根據此邏輯,第 kk 個向量 vkv_k 不能位於前 k−1k-1 個向量生成的子空間中,導致有 2n−2k−12^n - 2^{k-1} 種可能的選擇。

此類矩陣的總數(即 GL(n,F2)GL(n, \mathbb{F}_2) 的階數)為:

N=∏k=0n−1(2n−2k)=(2n−1)(2n−2)(2n−4)⋯(2n−2n−1)N = \prod_{k=0}^{n-1} (2^n - 2^k) = (2^n - 1)(2^n - 2)(2^n - 4)\cdots(2^n - 2^{n-1})

而所有可能的 n×nn \times n 矩陣總數為 (2n)n=2n2(2^n)^n = 2^{n^2}。

因此,機率 PP 為:

P=∏k=0n−1(2n−2k)2n2=∏k=0n−1(2n−2k2n)=∏k=0n−1(1−2k2n)P = \frac{\prod_{k=0}^{n-1} (2^n - 2^k)}{2^{n^2}} = \prod_{k=0}^{n-1} \left( \frac{2^n - 2^k}{2^n} \right) = \prod_{k=0}^{n-1} (1 - \frac{2^k}{2^n})

令 j=n−kj = n - k,我們可以將該表達式重寫為:

P=∏j=1n(1−12j)P = \prod_{j=1}^{n} \left(1 - \frac{1}{2^j}\right)

當 n→∞n \to \infty 時,該乘積收斂為一個常數:

P≈0.288788P \approx 0.288788

這意味著對於較大的 nn,隨機矩陣滿秩的機率約為 28.9%。


關於 BlockSec

BlockSec 是一家全端區塊鏈安全與加密合規服務供應商。我們建構產品與服務,協助客戶在協議與平台的全生命週期內進行代碼審計(包括智慧合約、區塊鏈與錢包)、即時攔截攻擊、分析安全事件、追蹤非法資金,並履行反洗錢/打擊資助恐怖主義(AML/CFT)義務。

BlockSec 已在知名會議上發表多篇區塊鏈安全論文,通報了多項 DeFi 應用的零日漏洞,阻斷了多次駭客攻擊並成功挽救超過 2000 萬美元資金,保護了數十億美元的加密貨幣資產。

訂閱最新動態
Bitget 3.875億美元鏈下違規事件:超越金鑰與合約的範疇
Security Insights

Bitget 3.875億美元鏈下違規事件:超越金鑰與合約的範疇

2026年9月24日,攻擊者利用第三方安全產品漏洞,獲取內部憑證並偽造提款指令,導致約3.875億美元從Bitget部分營運錢包(涵蓋Ethereum、其他EVM網絡、XRP Ledger、Zcash及TRON)轉出;私鑰及冷錢包未受影響。本文深入梳理事件路徑與資金流向,分析快速轉換為原生資產及生態恢復應對,提出機構縱深防禦框架,並說明授權區塊鏈滲透測試如何驗證跨層假設。

約1130萬美元損失:Multicall Router、Nostra|BlockSec Weekly
Security Insights

約1130萬美元損失:Multicall Router、Nostra|BlockSec Weekly

本報告涵蓋2026/09/14至2026/09/20期間,探討以太坊與Starknet上兩起合計損失約1130萬美元的安全事件。較大一起中,多重呼叫路由器將自身位址接受為分派目標,導致巢狀呼叫以路由器自身已授權身分而非外部呼叫者身分進入Safe錢包的Gateway模組,約2900枚`aEthrsETH`被轉出至攻擊者建立的Uniswap v4池。在Starknet上,Nostra預言機整合僅要求最少一個聚合來源,當三個設定價格來源中僅兩個達成聚合時,操縱的薄池報價與正常報價平均後將`NSTR`估值約49.52美元,支撐約350萬美元以高估抵押品借貸。

损失約3.2億美元:Liquid Network與Symbiosis遭攻擊事件 | BlockSec
Security Insights

损失約3.2億美元:Liquid Network與Symbiosis遭攻擊事件 | BlockSec

本報告涵蓋2026/09/07至2026/09/13,探討兩起造成約3.2億美元損失的安全事件,包括前份報告未提及、發生於2026/09/06的Liquid Network漏洞事件。較大者為該Liquid Network漏洞:Elements中的rangeproof驗證快取藉由雜湊四個欄位(其中兩個長度可變)串接而成金鑰,且未標示欄位邊界,導致某輸出的驗證結果被錯誤套用至另一個未經檢驗證明的輸出,使攻擊者得以憑空創造4,000枚無擔保L-BTC,並將幾乎全數兌回比特幣。在橫跨BNB Smart Chain、Ethereum與Rootstock的Symbiosis跨鏈橋比特幣路徑中,讀取比特幣存款的鏈下程式碼從存款人可自行控制的欄位取得其身份,並在未檢查手續費是否為負值的情況下自存款中扣除,使330聰的存款鑄造出`46,116,860,184.27388234 syBTC`;其須賣出所經之資金池僅有11.26 syBTC,致使流動性提供者與用戶損失約9.97 BTC(約77萬美元)。

Web3 最佳安全審計方

在上線之前驗證設計、程式碼與業務邏輯,對標業內最高安全標準。

BlockSec 審計
第10號全景事件:XOR線性破壞位置指紋方案