Notional Finance 因整數溢出漏洞遭駭,損失 1.73M 美元

iconMetaEra
分享
AI summary icon精華摘要
2026 年 9 月 4 日,Notional Finance 遭遇 1.73 百萬美元的攻擊,攻擊者利用託管合約中的整數溢出漏洞。ExchangeRate._convertToETH 函數中的缺陷允許在沒有足夠抵押品的情況下創建 fCash 持倉。此事件凸顯了在 DeFi 策略中強化風險與報酬比的重要性。使用技術分析進行加密貨幣交易的交易者應保持警覺,因為此類漏洞可能迅速改變市場動態。

作者:九九

編輯:77

背景

2026 年 9 月 4 日,知名去中心化借貸平台 Notional Finance 遭到攻擊,損失約 173 萬美元。以下是慢霧安全團隊針對本次攻擊事件的具體分析:

前置知識

在 Notional Finance V1 中,fCash 可被理解為具有到期日的現金債權。現金接收方在到期時收取款項,現金支付方在到期時支付款項。協議會將兩種倉位都記錄在投資組合中,並透過自由抵押品檢查來判斷一個帳戶建立倉位的健康狀況。

ERC1155Trade 的 safeTransferFrom 在此承擔資產記賬功能。當傳入的資產類型屬於 cash receiver 時,合約會調用 Portfolios.mintfCashPair,同時為 payer 記錄一筆負債,為 receiver 記錄一筆等額債權。此調用看似 ERC1155 轉賬,實則為一次 fCash 成對鑄造。

在新倉位寫入 Portfolio 後,需進行自由抵押品的計算與檢查。RiskFramework 合約會先將 payer 的 fCash 負債彙總為帶符號的 int256,再將各幣種餘額換算為 ETH 進行彙總。負數代表需支付的現金,正數代表帳戶擁有的現金或債權。此計算結果決定了交易是否允許進行。

仓位到期後,Portfolio 會調用 Escrow 的 portfolioSettleCash 函數,把到期的 fCash 變成現金餘額。帳戶再通過提款函數從 Escrow 取出對應的 DAI 或 USDC 資產。

fundamental reason

本次攻擊的核心漏洞在於 Escrow 實現合約所使用的 ExchangeRate._convertToETH,其中有一段代碼會將有符號整數的絕對值直接轉換為 uint128。

攻擊者先建立了兩筆相同 payer 的負債倉位,金額分別為 1 和 uint128.max。兩筆負債相加後正好得到 2^128。由於 payer 在風險計算中的付款被記為負數,因此 Escrow 的 convertBalancesToETH 傳入的關鍵參數為 -2^128。

但是此合約所使用的 Solidity 版本為 0.6.x,因此其絕對值 2^128 在強制轉換為 uint128 後會溢出變為 0,這筆負債也就未進入 ETH 計價結果。

最後在 mintfCashPair 函數中,僅要求 payer 的自由抵押品大於或等於零,因此在結果溢出為 0 後仍可通過此項檢查,一個沒有足夠資產覆蓋負債的帳戶仍可建立倉位。

攻擊步驟分析

1. 在前置交易(0xe1589a19…d60a)中,攻擊者首先創建了多個輔助合約,並調用 ERC1155Trade 合約的 setApprovalForAll 函數為輔助合約授權;此外還提前查詢了兩個 CashMarket 的到期日,計算好後續操作中需要傳入的三種 AssetId 參數的值。

2. 接著,攻擊者透過呼叫 ERC1155Trade 合約的 safeTransferFrom 函數,鑄造了一筆數額為 1 的 fCash pair,對應債券資產的 cashGroupId 為 2,到期日時間戳為 1788480000(2026 年 9 月 4 日 8 點整)。此過程會調用內部函數 _upsertAsset,分別更新 from 地址(攻擊合約)和 to 地址(接收合約 1)的負債與預期收入。

在 pair 銘刻完成後,Portfolios 會立即檢查攻擊合約的自由抵押品,而由於首次銘刻的數量過小,在當前匯率和精度下換算為 ETH 後會被四捨五入為零,因此首次檢查能夠通過。

3. 攻擊者隨後再次調用 ERC1155Trade 合約的 safeTransferFrom 函數,鑄造了一筆數額為 uint128.max 的 fCash 配對,此次對應的債券資產的 cashGroupId 為 2,但到期日時間戳為 1796256000(2026 年 12 月 3 日 8 點整)。

這裡有一個細節,攻擊者分別為兩個不同的地址鑄造了到期日不同的債券資產。這是因為在更新 from 地址的負債時,如果債券資產相同,則會直接對其進行累加,從而在處理加法時因溢出而 revert(合約使用了 safeMath 庫)。

4. 在第二次鑄造對從地址(攻擊合約)和至地址(接收合約 2)更新負債和預期收入後,mintfCashPair 函數會調用 freeCollateral 函數來計算從地址的淨抵押品頭寸,並檢查其是否大於等於 0 才為健康。

其中會先在 RiskFramework 合約中的 getRequirement 函數裡對 from 地址的兩次鑄造的負債總和進行相加:

其最終結果為 -2^128,接著會調用 Escrow 合約的 convertBalancesToETH 函數將這個數值轉換成 ETH 計價,而 convertBalancesToETH 函數又會調用 ExchangeRate 庫的 _convertToETH 函數進行計算處理:

追蹤至 _convertToETH 函數,可發現其會先取 balance 的絕對值,再使用 uint128 將 256 位轉換為 128 位;而上述 from 的負債總和為 -2^128,取絕對值後的 2^128 在進行 uint128() 強制轉換時會因超出上限而溢出為 0。這意味著協議誤判 from 地址的自由抵押品部位為 0,屬於健康狀態,從而通過了 mintfCashPair 函數中的最終檢查。

5. 隨後接收合約 2 將其倉位再次拆分給另外兩個輔助合約,這兩個 safeTransferFrom 仍會進入 mintfCashPair 函數。但由於此時的 payer 是第二次鑄造時的接收合約 2,其持有上一步獲得的龐大預期收入倉位,風險計算將其視為正向債權,從而通過了抵押品檢查,兩個輔助合約因此獲得了到期後可換成現金的 receiver fCash。

6. 攻擊者隨後發起了第二筆正式的獲利交易,在這筆交易中,分別為上一筆交易的兩個持有倉位的輔助合約結算其對應的債權,增加對應資產的現金餘額,之後調用 Escrow 合約的 withdraw 函數將其資產提幣獲利。

總結

此次攻擊事件的關鍵在於,攻擊者先利用兩個獨立倉位,將 payer 的負債總和湊成 2^128,再讓 Escrow 將這筆負債換算為 ETH。利用類型轉換中的溢出漏洞,將結果截斷為零,從而使風險檢查通過。

慢霧安全團隊建議項目方在進行類型轉換前必須做範圍檢查,超過 type(uint128).max 的結果直接拒絕,與此同時,所有涉及有符號金額、精度縮放和資產面額的邊界都應加入極值測試,尤其要覆蓋 uint128.max、uint128.max + 1 和負數絕對值這幾類輸入。可以參考或使用 Openzeppelin 的 SafeCast 庫進行轉換處理。

免責聲明:本頁面資訊可能來自第三方,不一定反映KuCoin的觀點或意見。本內容僅供一般參考之用,不構成任何形式的陳述或保證,也不應被解釋為財務或投資建議。 KuCoin 對任何錯誤或遺漏,或因使用該資訊而導致的任何結果不承擔任何責任。 虛擬資產投資可能存在風險。請您根據自身的財務狀況仔細評估產品的風險以及您的風險承受能力。如需了解更多信息,請參閱我們的使用條款風險披露