在過去一週(2026/08/17 - 2026/08/23),共有 2 起值得關注的安全事件,總損失約為 1,026 萬美元。
| 日期 | 事件 | 類型 | 估計損失 |
|---|---|---|---|
| 2026/08/18 | MAYAChain | 業務邏輯漏洞 | ~$1.76M |
| 2026/08/23 | Term Finance | 治理設計缺陷 | ~$8.5M |
選擇原因
- MAYAChain:入選原因是一個連鎖的會計與狀態驗證失敗,讓單筆精心構造的存款破壞了出款對帳流程,並在沒有真實資產支撐的情況下,將一個低流動性資金池的記錄餘額憑空放大,這顯示了多個各自看似有限的低層次缺陷,如何組合起來對一個跨鏈流動性網絡造成資金流失。
- Term Finance:入選原因是幾乎為零的治理參與率,使得沒有足夠的投票群體來否決惡意提案,且執行延遲背後也沒有守護者或取消機制,讓一名以極少資本投入的攻擊者得以主導投票、通過提案,並從六個協議金庫中盜取約 850 萬美元。
Best Security Auditor for Web3
Validate design, code, and business logic before launch
本週焦點:Term Finance
本週特別關注 Term Finance,因為此次失敗並非程式碼漏洞,而是治理設計上的缺陷——這類風險隨著協議為每個金庫各自部署獨立的鏈上 DAO 而日益增加:當幾乎無人參與治理時,投票控制權便可以被低成本收購,而金庫自身的治理機制反而成為攻擊面。
2026 年 8 月 23 日,Term Finance(一個建構於 Ethereum 上的固定利率借貸協議)因攻擊者奪取了其金庫鏈上治理的控制權,損失約 850 萬美元。由於幾乎沒有存款人曾經鑄造過某金庫的治理代幣,攻擊者只需花費約 0.5 ETH 便能取得該金庫絕對多數的投票權,通過協議的支持門檢查與最低參與率檢查,並執行一項惡意提案,撤銷該金庫的策略並轉移其資產;Term 有六個金庫以此方式被掏空 [1]。
背景
Term Finance 是建構於 Ethereum 上的固定利率借貸協議。Term Vaults 是建立在其上的一項獨立產品:每個金庫都是一個基於 Yearn V3 程式碼構建的 ERC-4626 金庫,其中一個元金庫(meta vault)接受單一資產,並將其分配到一組策略金庫中。Term 的框架會在部署每個金庫時,同時部署一個 Aragon OSx DAO 與一個 TokenVoting 合約,而該 DAO 對其隨附部署的金庫擁有升級與角色權限。因此,一個金庫自誕生起便帶有自己的治理層面,與誰來策劃該金庫或是否曾有資金分配進去無關。
治理決策由 TokenVoting 執行。每個金庫都有自己的份額代幣,以及對應的 Aragon GovernanceWrappedERC20 治理包裝代幣;在此次分析的 ETH Meta Vault 中,分別為 tmvETH 與 gtmvETH。任何持有份額代幣的人都可以呼叫 depositFor() 以 1:1 比例取得治理代幣,而任何治理代幣持有者都可以呼叫 delegate() 委託所產生的投票權。當創建提案時,TokenVoting.createProposal() 會記錄一個 snapshotBlock、一個 supportThreshold,以及一個由該快照時的代幣總供應量所推導出的 minVotingPower;投票權則於該區塊時從包裝合約中讀取。
漏洞分析
此次事件核心的治理合約是 TokenVoting。提案的執行僅由 _canExecute() 把關,對於一般(非提前)提案,該函式要求投票尚未被執行、提案已結束,且兩項檢查通過:isSupportThresholdReached() 與 isMinParticipationReached()。

這兩項檢查本身都是多數投票機制的正確實現,但它們衡量的都是相對比例,而非絕對數量。isSupportThresholdReached() 僅要求贊成票超過反對票達到設定的比率:

isMinParticipationReached() 要求投出的票數達到 minVotingPower,而該值本身是由快照時的 minParticipation * totalSupply 所推導出來的:

根本原因在於,這套治理設計沒有為推動一項提案所需的資金或參與廣度設定任何絕對下限。這兩道關卡都純粹是相對於已投出的票數與代幣供應量而定,而由於幾乎沒有 tmvETH 持有者曾透過包裝成 gtmvETH 來參與治理,該供應量以及隨之而來的最低參與率下限,都幾乎為零。因此,只需在一個近乎空無一人的選民群體中取得相對多數,即可通過這兩項檢查,而由於投票人數如此稀少,也沒有人能投下足以否決該提案的反對票。最低投票期限雖然延遲了執行,但由於沒有守護者或取消機制,這段延遲只是延後了該通過的提案,而非阻止它。
攻擊分析
攻擊者取得了某金庫治理代幣的控制性份額,然後利用它通過並執行了一項清空該金庫的提案;同樣的手法被套用在 Term 六個金庫上。以下分析以其中一個金庫為例,依據交易 0xd354a1...d3014129 與 0x9f273f...44c2e8a0。
-
步驟一:攻擊者透過一個 Mayan Finance 轉發器,將
0.5 ETH兌換為0.485 tmvETH,該轉發器負責路由此次兌換並將金庫份額交付給攻擊者。 -
步驟二:攻擊者將
0.485 tmvETH以 1:1 比例包裝為0.485 gtmvETH,取得了後續步驟所使用的投票權。 -
步驟三:攻擊者呼叫
propose(),該函式讀取其自身的投票權,並呼叫TokenVoting.createProposal()。合約記錄了快照,並讀取到該區塊的治理代幣總供應量僅為0.535 gtmvETH,因此攻擊者的持股佔整個選民群體約 90.66%。 -
步驟四:攻擊者呼叫
vote(),將其全部投票權投給贊成該提案,成為唯一的參與者。 -
步驟五:投票期結束後,攻擊者呼叫
executeProposal()。把關函式canExecute()首先檢查isSupportThresholdReached():由於攻擊者是唯一的贊成投票者,支持率遠超過 50% 的門檢;接著檢查isMinParticipationReached():攻擊者本身的投票權即已超過minVotingPower。兩項檢查均通過。 -
步驟六:該惡意提案隨後被執行,撤回了
tmvETH金庫分配給其策略的資金,將其轉換回金庫持有的流動性WETH,並讓攻擊者提走這些資產。此手法套用於 Term 的六個金庫,共造成約 850 萬美元的總損失。
結論
此次事件的成因是治理設計上的缺陷,而非程式碼錯誤:支持率與參與率檢查都純粹是相對值,因此在一個近乎空無一人的選民群體中,一份以低成本取得的多數持股完全不會遭遇反對票,而執行延遲背後也沒有守護者或取消機制作為支撐。凡是控制資產託管的治理機制,都應強制設定絕對的法定人數或參與率下限,並以守護者或取消機制,以及受監控的提案訊息流,來支撐任何的執行延遲,因為若無人能在延遲期間採取行動,時間鎖僅僅是延後了一項將會通過的提案。
本週其他事件
MAYAChain
2026 年 8 月 18 日,MAYAChain(一個基於 Cosmos-SDK 的跨鏈流動性網絡)因單筆精心構造的存款破壞了鏈上內部會計系統,損失約 176 萬美元。該存款讓有效的提款看似失敗,觸發了一條恢復流程,在沒有真實資產支撐的情況下,放大了一個低流動性資金池所記錄的原生代幣餘額;隨後攻擊者透過添加與撤出流動性,從中提取了這筆被放大的價值 [2]。經確認的鏈上資金流出約為 136 萬美元,主要為 20.83 BTC,加上剩餘的原生代幣持倉,官方估計損失總額約為 176 萬美元。
背景
MAYAChain 是一個基於 Cosmos-SDK 的跨鏈流動性網絡,其原生資產為 CACAO。外部鏈上發生的事件由驗證者觀察,並以「已觀察交易」的形式重播進 MAYAChain。當一項入站操作需要送出資產時,節點會排程一個或多個 TxOutItem,並在稍後將觀察到的出站交易與這些排程記錄進行對帳。所有資金池的資產都共同託管於共享的 Asgard 金庫中;每個流動性池只是對這一共享託管資產的一種會計狀態記錄,而非獨立分隔的餘額,提款則是依照資金池所記錄的餘額,從 Asgard 中支付。
交易帳戶(Trade accounts)是 MAYAChain 原生的會計持倉方式,用於如 ARB~ETH 與 ARB~LINK 等交易資產。交易帳戶的提款是透過帶有類似 trade-:ARB~LINK 備註的原生 MsgDeposit 訊息發起的。儘管使用者僅簽署了一筆原生交易,存款處理程式仍會重建一筆內部的「已觀察交易」,並儲存一個以該原生交易哈希為鍵值的 ObservedTxVoter。
漏洞分析
根本原因並非單一孤立的漏洞,而是一連串會計與狀態驗證缺陷彼此疊加而成。這些有問題的邏輯位於 MAYANode 的存款與出站處理程式中。
首先,在 handler_deposit.go 中,一筆原生交易中的每則訊息都會建立一個以交易哈希為鍵值的全新 ObservedTxVoter,並用 SetObservedTxInVoter() 儲存:
txIn := ObservedTx{Tx: tx}
txInVoter := NewObservedTxVoter(txIn.Tx.ID, []ObservedTx{txIn})
txInVoter.Height = ctx.BlockHeight()
txInVoter.FinalisedHeight = ctx.BlockHeight()
txInVoter.Tx = txIn
h.mgr.Keeper().SetObservedTxInVoter(ctx, txInVoter)
由於一批交易中的每則訊息都共用相同的 tx.ID,較晚的訊息會覆寫較早訊息所寫入的 voter 狀態,導致其出站排程的元資料被丟棄。
其次,在 handler_common_outbound.go 中,出站對帳流程從 voter.OutboundHeight(若該值為零,則使用 voter.FinalisedHeight)開始,並按簽署週期向前掃描:
outHeight := voter.OutboundHeight
if outHeight == 0 {
outHeight = voter.FinalisedHeight
}
for height := outHeight; height <= ctx.BlockHeight(); height += signingTransPeriod {
txOut, err = h.mgr.Keeper().GetTxOut(ctx, height)
...
}
當 voter 以此方式被覆寫後,掃描便會從存款完成確認的區塊高度開始,永遠不會檢查到那個包含已排程出站交易的區塊,因此處理程式會將這些出站交易誤判為缺失,並觸發削減(slash)恢復流程。
第三,在 helpers.go 中,恢復流程使用觀察到的原始數量,將「缺失」的資產估值為一筆 CACAO 補貼,且未針對資金池實際的資產深度設定任何上限:
f.stolenAsset = f.stolenAsset.Add(coin.Amount)
runeValue := pool.AssetValueInRune(coin.Amount)
f.subsidiseRune = f.subsidiseRune.Add(runeValue)
在一個資產側深度僅約 0.11 LINK 的資金池中,這種不設上限的換算方式可能將一筆觀察到的 LINK 數量,映射成一筆極其龐大的 CACAO 價值。同一段程式碼隨後在嘗試撥款轉帳之前,就先將這個被放大的資金池餘額提交到狀態中:
pool.BalanceCacao = pool.BalanceCacao.Add(f.subsidiseRune)
pool.BalanceAsset = common.SafeSub(pool.BalanceAsset, f.stolenAsset)
if err = mgr.Keeper().SetPool(ctx, pool); err != nil {
...
}
runeToAsgard := common.NewCoin(common.BaseAsset(), f.subsidiseRune)
if err = mgr.Keeper().SendFromModuleToModule(ctx, ReserveName, AsgardName, common.NewCoins(runeToAsgard)); err != nil {
ctx.Logger().Error("fail to send subsidy from bond to asgard", "error", err)
return err
}
由於資金池的寫入是在轉帳之前就已提交,若該轉帳因資金不足而無法完成,資金池的 BalanceCacao 也永遠不會被回滾。最後,在 handler_observed_txout.go 中,呼叫端吞掉了回傳的錯誤,將 voter 標記為完成後繼續執行,導致這種不一致的資金池狀態得以持續存在:
_, err = handler(ctx, m)
if err != nil {
ctx.Logger().Error("handler failed:", "error", err)
slashObservedOutbound("failed_outbound")
voter.SetDone()
h.mgr.Keeper().SetObservedTxOutVoter(ctx, voter)
continue
}
攻擊分析
攻擊者在單筆原生交易中組合利用了這些缺陷,然後從被放大的資金池中提取價值。以下分析依據 MAYAChain 交易 516BA14D...E9B7。
-
步驟一:攻擊者提交了包含 23 則訊息的原生交易
516BA14D...E9B7:20 筆trade-:ARB~ETH提款、2 筆trade-:ARB~LINK提款,以及最後一筆金額為一個基本單位的DONATE:ARB.LINK訊息。 -
步驟二:這些交易提款訊息建立了有效的排程出站交易,但最後的
DONATE訊息覆寫了同一原生交易哈希所對應的共享入站 voter,抹除了先前提款訊息的出站排程資料。 -
步驟三:當之後觀察到
ARB.LINK出站交易時,對帳掃描流程使用了被破壞的 voter 高度欄位,因而從未觸及那個包含已排程出站交易的區塊,於是將其視為缺失。 -
步驟四:削減恢復流程針對稀薄的
ARB.LINK資金池,對缺失的 LINK 進行估值,並將該資金池的CACAO一側放大了約4,945 萬 CACAO。本應為此補貼提供資金的 Reserve 對 Asgard 轉帳其實失敗了,因為 Reserve 僅持有約16.8 萬 CACAO,遠不足以支付這筆被放大的補貼金額;但被修改後的資金池餘額仍持續存在,而這項失敗的處理流程卻被標記為已完成。 -
步驟五:在稍晚的區塊高度,攻擊者向這個已扭曲的資金池添加了
100 CACAO及少量 LINK 的流動性。由於資金池的一側已被推高到嚴重失衡的程度,添加流動性的計算公式讓攻擊者相對於既有約7.31 億單位,獲得了約1 兆單位的 LP 份額。 -
步驟六:攻擊者立即撤出了幾乎全部持倉,從共享的 Asgard 金庫中,依照被放大的資金池餘額,取得了約
4,887 萬 CACAO以及98.82 LINK的支付,並將提取出的CACAO透過 MAYAChain 資金池換成 BTC 及其他資產。在這波拋售期間,CACAO的價格從約$0.115跌至接近$0.013的低點。
攻擊者提走的 4,887 萬 CACAO,若以攻擊發生前的價格計算,名義價值超過 500 萬美元,但這些價值並未全部轉化為外部實際獲益:其中大部分又被換回各個資金池,導致 CACAO 價格崩跌,而與攻擊者無關的套利者也趁著這波崩跌從中提取了價值。經直接確認的鏈上提取金額約為 136 萬美元,主要為 20.83 BTC;再加上攻擊者剩餘的 CACAO 及交易帳戶餘額,官方估計的損失總額約為 176 萬美元。
結論
此次事件的成因是一連串會計與狀態驗證缺陷的疊加,而非單一漏洞:任何一個環節單獨來看都不算致命,但它們合在一起,卻讓一筆精心構造的存款,將一個稀薄資金池所記錄的餘額,轉變成無真實支撐的價值。核心要求在於原子性與有界信任:一項會計狀態的變更與用於為其提供資金的轉帳,必須同成功或同失敗;一項失敗的恢復流程絕不能被標記為已完成;且針對低流動性資金池的估值必須設有上限。共享的入站狀態也絕不應被同一交易中較晚的訊息悄然覆寫。
參考資料
關於 BlockSec
BlockSec 是一家全端區塊鏈安全與加密合規服務提供商。我們打造各類產品與服務,協助客戶執行程式碼審計(涵蓋智能合約、區塊鏈及錢包)、即時攔截攻擊、分析安全事件、追蹤非法資金,並在協議與平台的整個生命週期中,協助客戶履行反洗錢/反資助恐怖主義(AML/CFT)義務。
BlockSec 已在多個頂級學術會議上發表了多篇區塊鏈安全論文,揭露了多項 DeFi 應用程式的零日攻擊,成功攔截多次黑客攻擊並挽回超過 2,000 萬美元的資產,同時保護了數十億美元的加密資產安全。
-
官方 Twitter 帳號:https://twitter.com/BlockSecTeam



