在報告期間(2026/08/22 - 2026/08/30),我們觀察到5起安全事件,估計總損失約為2270萬美元。
| 日期 | 事件 | 類型 | 估計損失 |
|---|---|---|---|
| 2026/08/20* | The Cosmos EVM Exploit Series(MANTRA、TAC、KiiChain,+3) | 算術下溢/上溢 | ~570萬美元* |
| 2026/08/27 | Moonwell | 價格操縱 | ~910萬美元 |
| 2026/08/28 | Ajna | 業務邏輯不當 | ~77.5萬美元 |
| 2026/08/28 | Rain Card 合約漏洞利用系列(Avici、Tria 等) | 簽名驗證繞過 | ~110萬美元† |
| 2026/08/30 | Tectonic | 價格操縱 | ~600萬美元‡ |
*Cosmos EVM 系列於08/20(MANTRA)開始,早於本週的報告期間,未在上週報告中涵蓋;為求完整性,此處一併列出。根據官方 Cosmos 事後分析報告,以8月19日的價格計算,攻擊者在六條受影響鏈上共套現約570萬美元(約287萬美元透過去中心化交易所,約285萬美元透過中心化交易所,目前已被凍結)。在已知代幣數量的情況下,名義被抽走的金額更大(TAC 約30億枚 TAC,約750萬美元;MANTRA 7.209億枚代幣,約360萬美元;KiiChain 1.483億枚 KII),但多數代幣未被出售、已被凍結,或可在鏈上追回。
†約110萬美元是受該共享合約影響的 Rain 支援計畫的估計總損失;Avici 和 Tria 是其中兩個最大的,分別揭露約500,859美元(1,685名用戶)和431,945美元(636名用戶)的損失。
‡約600萬美元是在鏈上回滾前橋接至以太坊的已實現損失。被抽走總額的估計範圍從約7400萬美元(在攻擊者錢包中追蹤到)到約1.195億美元(總市場流出量)不等;大部分資金留在 Cronos 上,並在驗證者將鏈回滾至漏洞利用前的狀態時被抹除。Tectonic 和 Cronos 均未確認最終損失數字。
Web3最佳安全審計商
在上線前驗證設計、程式碼與業務邏輯
本週焦點:The Cosmos EVM Exploit Series(於 TAC Chain 上追蹤)
本系列事件之所以入選,是因為它揭示了共享基礎設施漏洞揭露方式的問題:由於該漏洞被誤判為並非嚴重的安全威脅,其修復是以無聲的公開修補程式形式發布,而非透過協調一致的私下分發,隨後一個第三方分叉公開描述了漏洞利用路徑,使所有尚未修補該模組的鏈全部暴露在風險之中。
在2026/08/20至08/25期間,攻擊者運用一條結合了共享 cosmos/evm 模組中兩個漏洞的單一攻擊鏈,橫跨六條 Cosmos EVM 鏈進行攻擊。這兩個漏洞都位於協調 EVM 狀態與 Cosmos x/bank 帳本的程式碼中:一個是餘額下溢,另一個是與之對應的上溢,兩者在一筆供應量中性的交易中串連在一起。
根據官方 Cosmos 事後分析報告,該漏洞是在4月透過漏洞獎勵計畫回報的,最初被誤判為不會威脅到正式運營中的資金,因此走的是無聲的公開修補流程,並於08/19在 v0.6.2 和 v0.7.2 版本中發布。08/20,一個第三方分叉公開描述了漏洞利用路徑,數小時後首批資金被抽走,首先受害的是 MANTRA(08/20),接著是 TAC 和 KiiChain(08/22)[1][2]。在全部六條鏈上,攻擊者以8月19日的價格計算共套現約570萬美元(約287萬美元在去中心化交易所出售,約285萬美元在中心化交易所出售,目前已被凍結)[1]。
本報告詳細分析 TAC Chain 作為範例;它是本系列事件中受創最嚴重的鏈,質押池的名義損失約為750萬美元 [3]。
背景
TAC Chain 同時運行 Cosmos SDK 和 EVM。一個 tac1... 位址和一個 0x... 位址共享相同的底層20位元組,因此同一個位址可以先被建立為 Cosmos 歸屬帳戶(vesting account),之後再在其上部署一個 EVM 合約。這並非兩個獨立的帳戶:同一個位址同時承載歸屬狀態和合約程式碼。這兩者並行運作,TAC 透過位於固定位址的預編譯合約,將原生 Cosmos 操作(如質押)暴露給 EVM 呼叫者使用,因此一個 EVM 合約可以像呼叫其他任何合約一樣呼叫這些操作。
一個歸屬帳戶的銀行總餘額(Bank total balance)包括已鎖定的代幣。已鎖定部分在歸屬(vest)之前無法轉移,而可支配餘額(spendable balance)則是從總餘額中減去鎖定金額後剩餘的部分。當 EVM 載入一個帳戶時,它會用可支配餘額來初始化 StateDB 餘額,執行期間的合約轉帳操作作用的就是這個餘額。


在一筆交易結束時,StateDB 中的餘額變化會被結算回 x/bank(作為權威帳本),只有在那裡結算完成的部分才是真實的、可轉移的 TAC。TAC 運行的是 v0.7.x 版本線,該版本會直接將一個 EVM 餘額寫入 x/bank,但前提是該數值能夠通過 uint256 到 int256 的轉換,因此接近 2^256 的餘額根本無法被結算。
已鎖定的代幣無法被轉移,但仍然可以被委託用於質押。委託操作檢查的是包含鎖定代幣在內的銀行總餘額,然後透過 DelegatedVesting 維持歸屬限制。對於一個 total=1 的完全鎖定帳戶,委託該單位會將銀行總餘額降至 0 並更新 DelegatedVesting,而可支配餘額在委託前後都正確地保持為 0。
漏洞分析
存在問題的元件是共享 cosmos/evm 處理程式中的餘額同步邏輯,該處理程式在每次有狀態的質押預編譯呼叫時都會執行,其暴露於位址 0x0000...0800 的質押預編譯合約中,並在此提交中實作 [4]。它透過未經檢查的 uint256 算術運算來協調 EVM 餘額與 Cosmos 帳本,這暴露出兩個互補的缺陷:質押寫回路徑上的餘額下溢,以及普通加法路徑上與之對應的上溢。兩者單獨存在時都不危險;風險來自它們在共享算術路徑上的結合。
**第一個缺陷是餘額寫回時的下溢。**當一次有狀態的質押預編譯呼叫執行時,原生 Cosmos 操作會在 BeforeBalanceChange 和 AfterBalanceChange 這兩個鉤子之間執行,而 AfterBalanceChange 負責將原生餘額變化反映回 EVM 的 StateDB。


委託操作是針對銀行總餘額進行驗證的,因此一個完全鎖定的帳戶可以通過委託檢查,即使其可支配餘額仍為 0。原生委託操作會從銀行總餘額中扣除委託金額,並發出一個 coin_spent 事件。缺陷就在於 AfterBalanceChange 如何處理這個事件:它並非重新載入帳戶目前的可支配餘額並將其賦值,而是重放 coin_spent 的金額,以 stateDB.SubBalance(spender, amount) 的方式從現有的 EVM 餘額中扣除。

由於 SubBalance 使用未經檢查的 uint256 算術運算進行減法運算,任何 EVM 餘額小於事件金額的帳戶都會發生下溢。對於一個 EVM 餘額為 0、coin_spent 金額為 1 的帳戶,0 - 1 會回繞為 MAX_UINT256。

**第二個缺陷是加法路徑上與之對應的上溢。**每一筆餘額轉帳都會透過同一個 StateDB,在發送方執行 SubBalance,並在接收方執行 AddBalance,因此兩個方向共用同一條算術路徑。

兩者最終都會呼叫相同的 stateObject 基礎函式,其中 AddBalance 和 SubBalance 分別計算 new(uint256.Int).Add(s.Balance(), amount) 和 .Sub(s.Balance(), amount),未進行任何範圍檢查。正如減法會在低於 0 時發生下溢一樣,足夠大的加法也會在超過 MAX_UINT256 時發生上溢並回繞。

這兩個缺陷是互補的。單獨來看,下溢產生的 MAX_UINT256 餘額是不活躍的,因為接近 2^256 的數值無法通過結算到 x/bank 所需的 uint256 到 int256 轉換。而未經檢查的加法正是其對應部分:它是唯一能將如此巨大的餘額帶回結算上限以下的路徑。
攻擊分析
以下分析基於交易 0xae4e9b...da46fc。
-
步驟1:在交易 0x4da591...df1af7 中,攻擊者部署了一個
CREATE2工廠合約,以便在攻擊合約實際部署之前就能計算出其位址0x5711...c978。 -
步驟2:在交易 95F43742...6A885BA 中,攻擊者使用
MsgCreateVestingAccount在該未來合約位址上轉入並鎖定一個基本單位(1utac),使其獲得一個能通過委託檢查的銀行總餘額,同時使其可支配餘額保持為0。

- 步驟3:在交易 0x2400f8...c57c81 中,攻擊者使用
CREATE2將攻擊合約部署到同一個0x5711...c978位址上,使其同時成為一個 Cosmos 歸屬帳戶和一個 EVM 合約,這樣一個外部帳戶就可以支付 gas 費用,而該合約位址仍然是可支配餘額為spendable=0的委託人。

-
步驟4:攻擊合約透過質押預編譯合約委託了那個被鎖定的基本單位。質押流程接受了針對銀行總餘額的委託操作,隨後的餘額寫回操作便將該合約的 EVM 餘額回繞為
MAX_UINT256。 -
步驟5:回繞後的
MAX_UINT256餘額無法原樣結算回 Cosmos 帳本,因此攻擊合約首先將其降低到一個可結算的數值。它將幾乎全部餘額發送給bonded_tokens_pool(該鏈上最大的帳戶,持有所有已質押的 TAC),並選擇了一個能讓該資金池餘額因加法上溢而回繞為0的金額。由於該金額是從攻擊合約自身的MAX_UINT256中扣除的,攻擊合約最終持有的正好是該資金池原先的餘額,並未產生任何新的供應量。將資金池餘額清零可以取得其全部餘額,這是這種上溢能產生的最大收益,這也正是攻擊者一開始就以該鏈上最大帳戶為目標的原因。 -
步驟6:攻擊合約隨後將這筆餘額(2,985,651,403.40 TAC)轉移到了攻擊者的位址。

我們在交易層面的分析與官方 Cosmos 事後分析報告一致,該報告描述了兩個串連的漏洞:上述下溢產生了異常的餘額,而攻擊分析步驟5中的上溢則將其轉換為資金池的真實資金 [1]。一份較早發布的 KiiChain 事後分析報告(早於官方報告發布)則認為至少涉及三個上游缺陷,且只有下溢問題已被修補 [2]。獨立的媒體報導總結了關於究竟還有多少上游缺陷尚存的這一分歧 [5]。
結論
Cosmos EVM Exploit Series 的根本原因在於 EVM 層與 Cosmos 層對同一筆餘額的記帳方式不一致,再加上從未經過邊界檢查的算術運算。當兩個運行環境共享同一本帳本時,它們必須在每個單獨帳戶的層面上對餘額語義達成一致,並且每一次餘額變化都必須檢查是否會發生上溢或下溢。由於該缺陷存在於一個共享模組中,而非任何單一鏈自身的程式碼,因此一個缺陷就暴露了所有運行該模組的鏈,這正是一個單一漏洞演變成多鏈事件的原因。除了程式碼層面之外,這起事件也是一堂嚴重性評估的教訓。該漏洞最初被判定不會威脅到正式運營中的資金,因此其修復是以無聲的公開修補程式形式發布的;等到這一判斷被糾正時,該修補程式已經公開,而第三方對漏洞利用路徑的揭露,則將這一誤判轉變成了六條鏈遭到攻擊的結果。一個能夠轉移真實資金的共享基礎設施漏洞,從一開始就需要進行私下、協調一致的分發,而不是任何人都能解讀的無聲公開修補程式。
本週其他事件
Moonwell
2026/08/27,Base 鏈上的 Moonwell 遭到攻擊,損失約910萬美元,攻擊手法是將抵押品記帳膨脹與對 MAMO(其核心市場中掛牌的一種低流動性資產)的預言機價格操縱結合在一起。除了推高 MAMO 的價格外,攻擊者還直接將 MAMO 轉入 mMAMO 市場合約而不鑄造份額,這提高了每個份額的支撐價值,並在價格上漲的基礎上進一步膨脹了抵押品的價值。針對這種雙重膨脹的抵押品,攻擊者在 cbBTC、WETH、USDC 和 wstETH 上進行了約1103萬美元的總借款,經過清算後留下約913萬美元的剩餘債務 [6][7]。
漏洞分析
Moonwell 的核心市場運行在 Compound v2 程式碼上,其中一個市場份額(mMAMO)的價值為 (cash + totalBorrows - totalReserves) / totalSupply。這裡結合了兩個弱點。首先,MAMO 的供應上限只檢查正規的鑄造路徑,因此直接將 MAMO 轉入 mMAMO 合約會增加市場的現金量而不鑄造份額;這會提高計算出的兌換率(exchangeRateStored()),進而抬升每一個現有份額的抵押品價值,同時完全繞過了上限。其次,儘管流動性稀薄,MAMO 仍以 50% 的抵押係數被列為抵押品,因此其預言機價格可以用不大的資金量來撬動。由於抵押品價值的計算方式是份額數量乘以兌換率再乘以預言機價格,兌換率和價格都是可被操縱的環節,同時膨脹這兩者會成倍放大針對流動性遠更充足的資產的借款能力。
攻擊分析
以下分析基於交易 0x09687d...395593e。此次操作以約194.7萬美元啟動(799 ETH 兌換為 USDC 並橋接至 Base);一旦借入的資產被循環利用,MAMO 的總購買量達到約750萬美元。
-
步驟1:攻擊者透過正規方式提供
MAMO以鑄造mMAMO,隨後在兩筆未觸發任何Mint事件的交易中,直接將53,393,290 MAMO轉入mMAMO合約,將該市場計算出的兌換率提高了約3.68倍,並重新估值了攻擊者剛剛鑄造的mMAMO份額以及其他所有持有者的份額。 -
步驟2:攻擊者在流動性稀薄的情況下於各 DEX 交易池中買入
MAMO,將MAMO/USD報價從約$0.0106推高至約$0.43。

- 步驟3:在兌換率和價格都被雙雙膨脹的情況下,攻擊者完成了18筆跨
cbBTC、WETH、USDC和wstETH的借款(總計約1103萬美元),然後轉換並整合了這些收益,透過 CCTP 將約8.729萬 USDC橋接到以太坊,並將其兌換為約8.728萬 DAI。
結論
根本原因在於 Moonwell 同時在兩個可被獨立操縱的層面上,對一種低流動性抵押品資產進行了估值:其一是預言機價格,稀薄的流動性讓攻擊者能以不大的資金量撬動它;其二是其憑證代幣的兌換率,由於供應上限只守護了正規的鑄造路徑,直接轉入標的資產就能使其膨脹。由於抵押品價值是這兩者的乘積,同時膨脹兩者就會成倍放大針對流動性遠更充足的資產的借款能力。借貸市場應將抵押品的價格和份額記帳方式都視為可被操縱的環節:將未經請求的轉帳排除在兌換率計算之外,並將保守的抵押係數與具流動性意識的定價,以及針對低流動性資產的嚴格供應與借款上限結合起來。
Ajna
2026/08/28,Ajna 在其清算路徑上的一個業務邏輯缺陷導致七個以太坊資金池遭到攻擊,損失約77.5萬美元。Ajna 是一個不使用外部預言機的借貸協議;它透過從桶(bucket)流動性推導出的 LUP(最低已使用價格,Lowest Utilized Price)為部位定價。攻擊者首先操縱 LUP 以製造出一個嚴重資不抵債的部位,然後讓協議在協議仍持有的、遠高於市場價的荷蘭式拍賣價格上清算一個受控部位,使得桶存款憑證按其在計價代幣中的面值被消耗以償還債務,而攻擊者則以此換得真實的抵押品 [8]。
背景
Ajna 是一個無需許可的協議,任何人都可以像建立 Uniswap 資金池一樣,為配對代幣建立一個供應與借款資金池。每個資金池被劃分為多個「桶」(buckets),類似於 Uniswap V3 中的價格刻度(ticks),每個刻度代表每單位抵押品所對應的固定計價代幣數量。放貸人根據自己偏好的貸款價值比選擇一個桶,並在該桶中提供流動性,作為回報獲得 LP(對該桶存款的求償權)。由於沒有外部預言機,LUP 是直接從桶存款分佈和總債務推導出來的。
一旦一個部位的「閾值價格」(其債務除以其抵押品)超過 LUP,該部位就變得可被清算。此後任何人都可以透過提交保證金來將其 kick(觸發)進入清算,這會開啟一場荷蘭式拍賣。拍賣價格與現貨市場無關:它從參考價格的32倍開始,每小時減半,並且在第一個小時內(一段緩衝期)保持不變,之後才開始衰減。
被拍賣的抵押品可以透過兩種方式被取得。呼叫者可以呼叫 take(),以目前的拍賣價格支付計價代幣來取得抵押品;也可以呼叫 bucketTake(),該函式會使用選定桶的計價代幣存款來購買被拍賣的抵押品並償還借款人的債務。在 bucketTake() 中,取得的抵押品會計入該桶,因此是該桶的放貸人取得了這些抵押品;對於一次套利式的桶接盤(arbitrage bucket take),呼叫者還會額外獲得基於桶價格與拍賣價格之間價差計算的 LP 獎勵,作為觸發該次清算的激勵。這種設計假定接盤者只有在拍賣價格已經跌到或低於抵押品實際價值時才會出手:由於沒有預言機,這種下降式拍賣正是 Ajna 發現公允價格的方式,而一個桶的放貸人則透過以低於自己桶價格的價格取得抵押品來獲利。拍賣本身只有在借款人的債務被清償或該筆貸款重新恢復足額抵押時才會結束;另有一條獨立的結算路徑,會在72小時的寬限期後(或更早,一旦沒有剩餘抵押品時)解決一場拍賣,吸收任何殘餘的壞帳並將剩餘的抵押品歸還給借款人。
這套機制中有三個實作細節決定了一次接盤(take)所產生的數值。首先,拍賣價格並非源自市場,而是按照 32 * max(kickMomp, neutralPrice) * 2^(-max(elapsedHours - 1, 0)) 進行衰減,因此在第一個小時的緩衝期內保持不變,之後才開始下降,在該緩衝期結束後不久,價格仍會維持在其參考價格的約32倍附近。

其次,只有當借款人在扣除罰金前的債務已經處於資不抵債狀態時,一次 kick(觸發)才會被接受:_kick() 會先執行 _isCollateralized() 檢查,如果不符合條件則以 BorrowerOk() 回退,然後才會加上三個月利息的罰金,因此該罰金會拉高 kick 後記錄的債務,而並未幫助該部位達到觸發資格。

第三,一場拍賣的首次接盤(take)會在計算該次接盤的償還金額和抵押品數量之前,先給借款人的債務加上7%的罰金,因此單獨一次 bucketTake() 未必能夠自行清償全部債務。

漏洞分析
存在問題的合約是 Ajna 資金池合約(0xad24...178e);本文所描述的清算邏輯即取自該已驗證的原始碼。在 bucketTake() 內部,協議按面值使用一個桶的計價代幣存款憑證來償還拍賣中借款人的債務,並且在一次套利式桶接盤中,根據桶價格與拍賣價格之間的價差向接盤者支付 LP 獎勵。
它從未檢查這些存款憑證的實際可回收價值,也未檢查拍賣價格是否在經濟上合理;它只是簡單地假定這些憑證日後可以按面值兌回。它所依賴的這兩個價格是各自獨立計算的,且彼此之間從未經過相互驗證:LUP 跟隨桶存款分佈和資金池的總債務變化,而拍賣價格則純粹根據自 kick 時固定的參考價格起算的經過時間進行衰減。因此一項憑證的鏈上面值可能與其實際能夠回收的價值產生偏離,而 bucketTake() 仍然會按照該面值來結算債務。
在內部,bucketTake() 透過 _takeBucket() 路由至 _rewardBucketTake(),在一次套利式接盤中,接盤者的 LP 獎勵計算方式為取得的抵押品數量乘以桶價格與拍賣價格之間的價差。

攻擊分析
以下內容基於對 cbETH 資金池(七個受影響資金池之一)的鏈上分析,使用了交易 0x8a8793...016e64 和 0x12dfde...14e4f5。
- 步驟1:在準備階段的交易中,攻擊者向桶索引
2000(一個遠高於市場的價格)新增了約49.343WETH的計價代幣流動性。以這個被膨脹的桶作為支撐,攻擊者開設了一個幾乎沒有抵押的部位,抵押約0.001cbETH來借入約49.319WETH。這個部位幾乎不持有任何抵押品,因此它本身並非攻擊目標;它是為兩個準備工作而服務的。首先,作為一筆資不抵債的債務,一旦市場恢復正常,它會拉低LUP。其次,由於存入桶2000的49.343WETH中幾乎全部都已被借出,只留下微量餘額,桶2000就此留下了一項存款憑證,其面值(約49.343WETH)遠遠超過其實際可回收的價值;而這項受損的憑證,正是攻擊者稍後在步驟4中以面值花費的彈藥。

-
步驟2:在同一筆準備交易中,攻擊者使用第二個受控地址
0x02d329...6f5f,以正常的價格水準開設了一個借款人部位,抵押約48.128cbETH的抵押品並以此借入約49.319WETH。這使得市場價格恢復到正常區間,讓第一個部位嚴重資不抵債,而第二個部位則恰好處於其健康度的臨界值附近。這第二個持有真實抵押品的部位,才是攻擊者真正打算抽走資金的目標;而那個資不抵債的第一個部位,只是用來撬動LUP的槓桿。 -
步驟3:接著攻擊者觸發(kick)了第二個部位(即由
0x02d329...6f5f開設的部位)進入拍賣,而非那個資不抵債的第一個部位。一次 kick 只有在該部位相對於目前LUP而言,其扣除罰金前的債務已經處於資不抵債狀態時才會被接受,而此時那個資不抵債的第一個部位已將LUP拉低到足以使第二個部位的累計債務達到這一標準。只有在通過資格檢查之後,協議才會加上三個月利息的 kick 罰金,這也是為何 kick 後記錄的債務達到約49.362WETH的原因。此次 kick 開啟了該部位的荷蘭式拍賣。

- 步驟4:攻擊者等到一小時緩衝期剛結束的時候,此時拍賣價格才剛開始衰減,仍接近參考價格的32倍。攻擊者在桶
2000(正是目前持有那項受損憑證的同一個桶)上對第二個部位呼叫bucketTake(),以這個仍然過高、遠高於市場的價格對其進行了清算。由於這是該場拍賣的首次接盤,協議在計算此次接盤的償還金額和抵押品數量之前,先給借款人的債務加上了7%的罰金。由於抵押品是按拍賣價格計價的,消耗桶2000中約49.34WETH的存款只償還了該筆債務的一部分,並且只移除了約1.51cbETH的抵押品,大部分抵押品保持未動,而呼叫者則收取了大量基於價差計算的LP獎勵。

-
步驟5:攻擊者透過
removeCollateral()兌回了這些LP獎勵,將部分利潤以抵押品的形式提取出來。 -
步驟6:為了收尾,攻擊者取得了一筆 Balancer 閃電貸款,並呼叫 Ajna 的
take()來償還bucketTake()遺留下來的剩餘債務,這一次是用真實的計價代幣支付,直到借款人的債務歸零,拍賣隨之結束。隨後一次repayDebt()呼叫則以quoteRepaid=0的方式提取出了現已無負擔的抵押品,約46.51cbETH:沒有為此返還任何計價代幣。這筆收益是以資金池為代價換來的。這一虧空並未消失,而是發生了轉移:隨著第二個部位被平倉,第一個部位那筆仍未償還、幾乎沒有支撐的貸款,就作為壞帳留在了資金池中,由其他放貸人承擔。

結論
根本原因在於,Ajna 的清算路徑是按面值來結算拍賣中借款人的債務相對於一個桶的存款憑證,而未檢查其真實可回收價值,也未檢查拍賣價格是否在經濟上合理。正是這一缺失的檢查,讓一項被人為製造出來的受損憑證能夠按面值被花費,用以抽走真實的抵押品,將虧空作為壞帳留在資金池中。清算路徑應當按存款憑證的真實可回收金額而非面值來對其估值,在完成一次接盤之前確認拍賣價格在經濟上合理,並防範消耗那些已因資金池既有壞帳而受損的憑證。受影響的資金池合約是不可變更的,且未提供任何管理性的暫停功能,因此一旦資金開始被抽走,就沒有辦法將其阻止;用戶只能自行退出。
The Rain Card Contract Exploit Series(於 Avici 上追蹤)
2026/08/28(UTC),Rain 共享的 Solana 卡片抵押品程式的一個過時版本,透過一個 Ed25519 簽名驗證繞過漏洞遭到利用。透過讓該程式接受一個偽造的管理員批准,攻擊者取得了用戶抵押品帳戶的控制權,並抽走了其中持有的代幣餘額。由於該缺陷存在於跨多個 Rain 驅動的卡片程式共用的程式碼中,單一漏洞便一次性暴露了所有這些程式:此次攻擊估計共抽走了約110萬美元,其中 Avici 和 Tria 是兩個損失最大的,分別揭露了約500,859美元(1,685名用戶)和431,945美元(636名用戶)的損失 [9]。
以下分析以 Avici 作為範例。
背景
在 Solana 上,簽名檢查是在交易處理過程中,由一個原生的 Ed25519 預編譯程式來執行的:如果被引用的簽名無效,整筆交易就會失敗。業務程式並不會直接收到這個結果;它會(透過 Instructions sysvar)檢視同一筆交易中的其他指令,並依賴驗證已經通過這一前提。當用戶為一張 Rain 驅動的卡片(如 Avici)儲值時,其餘額會被存放在由共享 Rain 程式管理的個人抵押品帳戶中,該帳戶的代幣管理權限(token authority)源自於該程式,因此該帳戶的管理員可以透過該程式的轉帳流程來移動該帳戶中的資產。
更改一個抵押品帳戶的管理員需要兩個簽名。其中一個必須來自協議指定的管理員;另一個簽名者則沒有特定的身份要求。這種設計依賴於鏈下授權:一旦協議管理員已對管理員變更訊息進行簽名,該程式就會將其視為已獲批准。
一個 Ed25519 驗證指令以一個表示待檢查簽名數量的單一位元組開始,接著是一個填充位元組,之後每個簽名對應一個 Ed25519SignatureOffsets 結構。每個結構不僅指定了簽名、公鑰和訊息各自的位元組偏移量,還指定了應從哪個指令索引位置讀取這些內容 [10]。這些索引是一項有意設計的功能:它們讓一次驗證能夠從交易中任何被索引的指令的資料中讀取其輸入,例如可以在不複製資料的情況下,驗證針對另一個指令資料的簽名。原生驗證器只是單純地讀取這些偏移量和指令索引所指向的內容。

漏洞分析
此缺陷存在於抵押品程式(3zVB...yBzDuc)中:它信任一個公鑰,卻不確認該公鑰是否確實就是驗證器實際檢查過的那一個。為了記錄批准管理員變更的簽名者,該程式會從其所檢視的 Ed25519 驗證指令內部的一個固定位置讀取一個公鑰,然後僅憑一次驗證的通過,就將其視為該公鑰持有者已對管理員變更訊息進行了簽名的證明。
它從未確認的是那次驗證實際檢視的究竟是哪個位置。由於這些指令索引欄位是由呼叫者控制的,而運行環境會將每個指令的資料都交給原生驗證器,因此一個驗證指令可以在其自身內容中放置一個公鑰,同時其指令索引卻將驗證器導向另一個不同的指令,從而驗證的是一個不同公鑰對一個來源不同的訊息所做的簽名。


因此存在兩種對「管理員金鑰」的讀取方式,兩者之間卻沒有任何約束將其綁定在一起:該程式信任的是位於其所檢視指令中的那個金鑰,而驗證器實際檢查的卻永遠是索引所指向的那個內容。管理員的金鑰可以作為資料出現在交易中,即使從未有任何在該金鑰下有效的簽名真正經過驗證。這種脫節正是該漏洞所在,而缺失的檢查正是那項本應強制要求驗證器實際驗證過的公鑰和訊息,必須與該程式所信任的公鑰和訊息完全一致的約束。
攻擊分析
以下分析基於交易 ZmpBgn...mqWL。
- 步驟1:攻擊者構建了一筆包含兩個 Ed25519 驗證指令、後接一個
SubmitSignatures指令的交易。指令0攜帶了攻擊者自己的公鑰(cafa…53db)以及對管理員變更訊息的一個真實簽名,使該筆交易中包含了一次真正有效的 Ed25519 驗證。

-
步驟2:指令
1將協議管理員的公鑰(a2fc…959a)放在了公鑰欄位中,卻在簽名欄位填入了偽造的0x09位元組,因此如果該指令自身的資料被實際驗證,這個指令原本應當會失敗。 -
步驟3:攻擊者將指令
1的 Ed25519 標頭設定為01003000000010000000700020000000。只有三個指令索引欄位起到了重新導向的作用,而解碼後這三個欄位全部為零,因此簽名、公鑰和訊息全都是從指令0中讀取的(位元組偏移量仍然指向該指令自身資料中的位置):num_signatures = 1, padding = 0 signature_offset = 48, signature_instruction_index = 0 public_key_offset = 16, public_key_instruction_index = 0 message_data_offset = 112, message_data_size = 32, message_instruction_index = 0因此第二次驗證重新讀取了指令
0,並重新驗證了攻擊者自己那個有效的簽名,而不是指令1中那個無效的0x09負載內容。 -
步驟4:攻擊者呼叫了
SubmitSignatures。該程式看到了兩次成功的 Ed25519 驗證,但在記錄第二個簽名者時,它讀取的卻是嵌在指令1中的協議管理員公鑰,因此它接受了該管理員金鑰作為一個已獲批准的第二簽名者。 -
步驟5:在這項虛假批准被記錄下來之後,攻擊者利用管理員變更流程,將受害者抵押品帳戶的管理員設為攻擊者本人,從而將這次驗證繞過轉化為對該帳戶的直接控制。

- 步驟6:在驗證呼叫者為抵押品管理員之後,該程式透過其轉帳流程,將資產從用戶的卡片抵押品代幣帳戶中轉出,並以其自身的程式衍生位址(PDA)作為該代幣帳戶的權限持有者來調用轉帳操作。

結論
The Rain Card Contract Exploit Series 的根本原因在於,該抵押品程式確認了一次原生的 Ed25519 驗證已經執行,卻從未確認其所信任的公鑰和訊息,是否確實就是驗證器實際檢查過的那些。一個依賴原生簽名驗證的程式,必須將其所使用的授權資料與該次驗證綁定在一起:要嘛要求採用一種自包含的 Ed25519 佈局,使其輸入從目前指令中讀取;要嘛就必須解析每一個被引用的指令,並將驗證通過的公鑰和訊息,逐位元組地與其實際據以行動的資料進行比對。同樣的缺陷之所以能在眾多 Rain 驅動的卡片程式中未經修補地持續運行,正是一個漏洞演變成多程式事件的原因。
Tectonic
2026/08/30,運行於 Cronos 上、採用 Compound 式借貸模式的 Tectonic 遭到攻擊,原因在於其低流動性治理代幣 TONIC 被允許以 20% 的抵押係數作為抵押品。攻擊者同時在兩個層面上膨脹了以 TONIC 計價的抵押品:一是對 tTONIC 兌換率進行直接轉帳操縱,二是配合 DEX 買盤將預言機價格推高。針對這種雙重膨脹的抵押品,攻擊者跨多個借貸市場進行了借款。約629萬美元(2,592枚 ETH)被橋接至以太坊,構成已實現的損失;大部分被抽走的資金留在了 Cronos 上,並在驗證者將鏈回滾至漏洞利用前的狀態時被抹除。Tectonic 和 Cronos 均未確認最終損失數字 [11]。
漏洞分析
根本原因在於將 TONIC(一種低流動性治理代幣)以 20% 的抵押係數列為抵押品。儘管 TONIC 具有治理作用,但其鏈上流動性極為稀薄,因此其估值能夠以有限的資金大幅撬動。與 Moonwell 那起攻擊事件一樣,這裡同時存在兩個可被操縱的層面:TONIC/USD 預言機價格,以及 tTONIC 憑證代幣的兌換率——單純將 TONIC 轉入該市場即可抬升後者,而無需抵銷對應的債務 [11]。隨後,任何非零的抵押係數都會將這種被膨脹的估值轉化為針對流動性遠更充足的資產的借款能力。
攻擊分析
以下對此次攻擊的重建,是基於鏈上情報以及一份詳盡的存檔節點重建分析 [11][12]。
-
步驟1:攻擊者向 Tectonic 提供了約500萬
USDC作為抵押品。在TONIC/USD預言機仍將該代幣估值在約$1.06e-8附近時,一個受控帳戶借入了約376.54萬億枚TONIC,並將其轉移到第二個帳戶。 -
步驟2:第二個帳戶正常供應了約41.87萬億枚
TONIC,並因此獲得了tTONIC(該市場的憑證代幣)作為回報。 -
步驟3:攻擊者未償還原本借入的
TONIC債務,而是將大部分剩餘的借入TONIC直接轉入tTONIC市場合約,藉此抬升了tTONIC的兌換率,並提升了第二個帳戶所持有的tTONIC的抵押品價值。 -
步驟4:利用被膨脹的
tTONIC抵押品,攻擊者借入了20萬USDC和約696萬枚CRO,然後在TONIC/USDC、TONIC/WCRO和TONIC/VVS交易池中買入約16.23萬億枚更多的TONIC,並將其重新導入該市場。這進一步抬升了兌換率,並推高了 DEX 現貨價格;Tectonic 的鏈下TONIC/USD報價機制隨後接受了一系列快速上漲的報價(從 UTC 時間12:19的約$1.06e-8,上漲到 UTC 時間12:49的約$2.08e-6)。這是外部層面的操縱。

-
步驟5:進一步借入約331萬
USDC和2121萬枚CRO,又買入了76.8萬億枚TONIC,同樣重新導入該市場,在抽走資金之前進一步強化了被膨脹的兌換率和被操縱的價格。 -
步驟6:在最後一筆交易中,攻擊者針對這種雙重膨脹的抵押品,在多個借貸市場中提取資金,共抽走了約5524萬枚
USDC、4565萬枚USDT、98枚WBTC、1,895枚WETH、1675萬枚CRO以及其他資產。在驗證者停止該鏈之前,約629萬美元(約2,592枚 ETH)被橋接至以太坊。在驗證者將狀態回滾至漏洞利用前的一個區塊、抹除鏈上餘額之後,區塊生產於2026/08/30 UTC時間23:49恢復(於08/31宣布);只有橋接出的約629萬美元構成已實現的損失。
結論
與本週稍早發生的 Moonwell 攻擊事件類似,這也是一起針對採用 Compound 式模式、且接受一種低流動性代幣作為抵押品的市場所發起的價格操縱攻擊,同時在兩個層面上膨脹了該抵押品的價值:預言機價格和憑證代幣兌換率。借貸協議應避免將低流動性資產列為抵押品,在必須支援此類資產時採用嚴格的供應與借款上限,並使用時間加權或具流動性意識的定價方式,使稀薄的現貨市場無法撬動預言機。兌換率這個層面也需要專門的防護:一個市場的抵押品記帳應排除對底層資產的未經請求的轉帳,這樣直接轉帳就無法在對應的債務仍然未償還的情況下膨脹憑證代幣的兌換率。對異常價格和借款活動的鏈上監控,可以進一步縮短在資金被橋接轉出之前的應對時間窗口。
參考資料
- [1] Cosmos EVM GHSA-7g4w-cg88-2cq2 事後分析報告
- [2] KiiChain 事件揭露
- [3] TAC Chain 事件揭露
- [4] cosmos/evm 餘額處理程式提交
- [5] crypto.news:Cosmos EVM 漏洞抽走 MANTRA、TAC 和 KiiChain 的資金
- [6] Blockaid 對 Moonwell 攻擊事件發出的警示
- [7] Moonwell:Base 上 MAMO 市場事件事後分析報告
- [8] Defimon 對 Ajna 攻擊事件發出的警示
- [9] crypto.news:Rain 合約漏洞利用事件從卡片用戶處抽走110萬美元
- [10] Solana 文件:Ed25519 程式
- [11] The Defiant:Cronos 在 Tectonic 攻擊事件後將鏈回滾
- [12] MASTR:Tectonic 存檔節點重建分析
關於 BlockSec
BlockSec 是一家全端區塊鏈安全與加密貨幣合規服務供應商。我們建構的產品與服務可協助客戶執行程式碼審計(包括智慧合約、區塊鏈與錢包)、即時攔截攻擊、分析事件、追蹤非法資金,並在協議與平台的整個生命週期中滿足反洗錢/反恐融資(AML/CFT)義務。
BlockSec 已在頂尖會議上發表多篇區塊鏈安全論文,回報了多起 DeFi 應用程式的零日攻擊,成功攔截多起駭客攻擊事件,挽回超過2000萬美元的損失,並為數十億美元的加密貨幣資產提供了安全保障。
-
官方 Twitter 帳號:https://twitter.com/BlockSecTeam



