過去1週間(2026/08/17 - 2026/08/23)、以下の2件の注目すべきセキュリティインシデントが発生し、合計損失額は約$10.26Mに達しました。
| 日付 | インシデント | タイプ | 推定損失額 |
|---|---|---|---|
| 2026/08/18 | MAYAChain | ビジネスロジックの欠陥 | ~$1.76M |
| 2026/08/23 | Term Finance | ガバナンス設計の欠陥 | ~$8.5M |
選定理由
- MAYAChain: 連鎖的な会計処理および状態検証の失敗により、単一の巧妙に作成されたデポジットがアウトバウンドの照合処理を破壊し、低流動性プールの記録上の残高を実際の裏付けなしに膨張させたため選定されました。これは、個々には限定的な複数の低レベルの欠陥が組み合わさることで、クロスチェーン流動性ネットワークからの資金流出につながることを示しています。
- Term Finance: ガバナンス参加率がほぼゼロだったため、悪意のある提案を否決する投票権者が存在せず、実行遅延を支えるガーディアンやキャンセル手段もなかったため選定されました。これにより、最小限の資本を持つ攻撃者が投票を独占し、提案を通過させ、6つのプロトコルボールトから約$8.5Mを流出させることができました。
Best Security Auditor for Web3
Validate design, code, and business logic before launch
今週の注目: Term Finance
今週Term Financeが取り上げられたのは、その失敗がコーディングのバグではなく、ガバナンス設計上の欠陥であったためです。このリスクは、プロトコルが各ボールトごとに個別のオンチェーンDAOを提供するにつれて拡大します。ほとんど誰も参加しない場合、投票の支配権を安価に買い取ることが可能になり、ボールト自体のガバナンスが攻撃対象となってしまいます。
2026年8月23日、Ethereum上の固定利率レンディングプロトコルであるTerm Financeは、攻撃者がそのボールトのオンチェーンガバナンスを乗っ取ったことにより、約$8.5Mを失いました。実質的にどのデポジターもボールトのガバナンストークンをミントしたことがなかったため、攻撃者はおよそ0.5 ETHでその投票権の超過半数を取得することができ、プロトコルの支持率基準および最低参加率基準を通過し、ボールトの戦略を取り消してその資産を移転する悪意のある提案を実行することができました。この手法によりTermの6つのボールトが資金を流出させられました [1]。
背景
Term Financeは、Ethereum上の固定利率レンディングプロトコルです。Term Vaultsはその上に構築された別のプロダクトです。各ボールトはYearn V3コードをベースに構築されたERC-4626ボールトであり、メタボールトは単一のアセットを受け入れ、それを一連の戦略ボールトに配分します。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()によってのみゲートされており、通常(早期でない)提案の場合、投票がまだ実行されていないこと、提案がクローズされていること、そして2つのチェックが通過すること — isSupportThresholdReached()とisMinParticipationReached() — が要求されます。

両方のチェックは多数決の正しい実装ですが、それぞれが絶対量ではなく相対的な比率を測定しています。isSupportThresholdReached()は、設定された比率を超えて賛成票が反対票を上回ることのみを要求します:

isMinParticipationReached()は、投じられた投票がminVotingPowerに達することを要求しますが、この値自体はスナップショット時点でのminParticipation * totalSupplyから導出されます:

根本原因は、このガバナンス設計が、提案を動かすために必要な資本や広さの絶対的な下限を設定していなかったことです。両方のゲートは、投じられた投票とトークン供給量に対して純粋に相対的なものであり、tmvETH保有者のほぼ誰もgtmvETHにラップしてガバナンスに参加したことがなかったため、その供給量、そしてそれに伴う最低参加率の下限は、ほぼゼロの状態でした。したがって、ほぼ空の投票者集団における相対的な多数派であれば、両方のチェックを通過するのに十分であり、投票者が非常に少なかったため、提案を否決できるような反対票を投じる者もいませんでした。最小投票期間により実行は遅延しましたが、ガーディアンやキャンセル手段が存在しなかったため、この遅延は提案の通過を防ぐことなく、単に先延ばしにするだけでした。
攻撃の分析
攻撃者はボールトのガバナンストークンの支配的なシェアを取得し、それを使ってそのボールトを空にする提案を通過させて実行しました。同じ手法がTermの6つのボールトすべてに適用されました。以下の分析は、トランザクション0xd354a1...d3014129および0x9f273f...44c2e8a0に基づき、1つのボールトを追跡したものです。
-
ステップ1: 攻撃者はMayan Financeのフォワーダーを通じて
0.5 ETHを0.485 tmvETHにスワップし、これがスワップをルーティングしてボールトのシェアを攻撃者に渡しました。 -
ステップ2: 攻撃者は
0.485 tmvETHを1対1で0.485 gtmvETHにラップし、以降のステップで使用する投票権を取得しました。 -
ステップ3: 攻撃者は
propose()を呼び出し、これが自身の投票権を読み取ってTokenVoting.createProposal()を呼び出しました。コントラクトはスナップショットを記録し、そのブロック時点でのガバナンス総供給量がわずか0.535 gtmvETHであることを読み取りました。つまり攻撃者の持ち分は投票者集団全体の約90.66%でした。 -
ステップ4: 攻撃者は
vote()を呼び出し、自身の投票権全体を提案への賛成票として投じ、唯一の参加者となりました。 -
ステップ5: 投票期間終了後、攻撃者は
executeProposal()を呼び出しました。ゲートcanExecute()はisSupportThresholdReached()をチェックしました: 攻撃者が唯一の賛成票投票者であったため、支持率は50%の基準を大幅に上回りました。次にisMinParticipationReached()をチェックしました: 攻撃者自身の投票権だけでminVotingPowerを超えていました。両方とも通過しました。 -
ステップ6: 悪意のある提案が実行され、
tmvETHボールトがその戦略に配分していた資金が撤退させられ、ボールトが保有する流動性のあるWETHに変換され、攻撃者がその資産を引き出すことができるようになりました。この攻撃はTermの6つのボールトすべてに適用され、合計で約$8.5Mの損失を引き起こしました。
結論
このインシデントは、コーディングのエラーではなくガバナンス設計上の欠陥によって引き起こされました。支持率および参加率のチェックが純粋に相対的なものであったため、ほぼ空の投票者集団において、安価に取得された多数派が対抗する投票に遭うことなく通過し、実行遅延の背後にガーディアンやキャンセル手段が存在しませんでした。資産の保管を管理するガバナンスは、絶対的なクォーラムまたは参加率の下限を強制すべきであり、実行遅延をガーディアンやキャンセル手段、および監視された提案フィードで支えるべきです。タイムロックは、その間に誰かが対処できない限り、通過する提案を単に先延ばしにするだけだからです。
今週のその他のインシデント
MAYAChain
2026年8月18日、Cosmos-SDKクロスチェーン流動性ネットワークであるMAYAChainは、単一の巧妙に作成されたデポジットがチェーンの内部会計を破壊したことにより、約$1.76Mを失いました。有効な引き出しが失敗したように見せかけることで、このデポジットはリカバリーパスを発動させ、低流動性プールの記録上のネイティブトークン残高を実際の裏付けなしに膨張させました。その後、攻撃者は流動性を追加および引き出すことで、その膨張した価値を流出させました [2]。オンチェーンで確認された流出額は約$1.36Mで、主に20.83 BTCでしたが、残存するネイティブトークンの保有分を含めると、公式の推定額は約$1.76Mとなります。
背景
MAYAChainは、ネイティブアセットがCACAOであるCosmos-SDKクロスチェーン流動性ネットワークです。外部チェーン上のイベントはバリデータによって観測され、観測済みトランザクションとしてMAYAChainに再現されます。インバウンドのアクションが資産をアウトバウンドで送信する必要がある場合、ノードは1つ以上のTxOutItemをスケジュールし、後にその観測されたアウトバウンドトランザクションをそれらのスケジュールされた記録と照合します。すべてのプール資産は共有のAsgardボールトに一括で保管されており、各流動性プールは独立した残高ではなく、その共有保管に対する会計上のポジションです。引き出しはプールの記録上の残高に対してAsgardから支払われます。
トレードアカウントは、ARB~ETHやARB~LINKなどのトレードアセットに対するネイティブなMAYAChainの会計ポジションです。トレードアカウントの引き出しは、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が上書きされていると、スキャンは確定したデポジットの高さから開始され、スケジュールされたアウトバウンドを保持するブロックを検査することがなくなるため、ハンドラーはそれらを欠落しているものとして扱い、スラッシュリカバリーパスを発動させます。
第三に、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に基づいています。
-
ステップ1: 攻撃者は、23個のメッセージを含むネイティブトランザクション
516BA14D...E9B7を送信しました: 20件のtrade-:ARB~ETH引き出し、2件のtrade-:ARB~LINK引き出し、そして最後に1ベースユニットのDONATE:ARB.LINKです。 -
ステップ2: トレードの引き出しは有効なスケジュール済みアウトバウンドを作成しましたが、最後の
DONATEメッセージが同じネイティブトランザクションハッシュに対する共有のインバウンドvoterを上書きし、以前の引き出しのアウトバウンドスケジューリングを消去しました。 -
ステップ3:
ARB.LINKのアウトバウンドが後に観測されたとき、照合スキャンは破壊されたvoterの高さフィールドを使用し、スケジュールされたアウトバウンドを保持するブロックに到達することがなかったため、それらを欠落しているものとして扱いました。 -
ステップ4: スラッシュリカバリーパスは、欠落しているLINKを薄い
ARB.LINKプールに対して評価し、プールのCACAO側を約49.45M CACAO膨張させました。この補助金に資金を供与すべきであったReserveからAsgardへの転送は失敗しました。Reserveが保有していたのは約168K CACAOのみで、膨張した補助金に対して大きく不足していたためです。しかし、変異したプール残高は持続し、失敗したハンドラーは完了とマークされました。 -
ステップ5: その後のある高さで、攻撃者は
100 CACAOと少量のLINKで歪んだプールに流動性を追加しました。プールの一方の側が大きく不均衡になっていたため、流動性追加の計算式は、既存の約731Mユニットに対して攻撃者に約1TのLPユニットを付与しました。 -
ステップ6: 攻撃者は即座にそのポジションのほぼ全体を引き出し、共有のAsgardボールトから膨張したプール残高に対して支払われた約
48.87M CACAOと98.82 LINKを受け取り、抽出したCACAOをMAYAChainのプールを通じてBTCおよび他のアセットにスワップしました。この売却の間、CACAOの価格は約$0.115から最安値の約$0.013まで下落しました。
攻撃者が引き出した48.87M CACAOは、エクスプロイト前の価格で計算すると名目上$5M以上の価値がありましたが、そのすべてが外部への収益として実現されたわけではありません。その多くはプールに再びスワップされ、CACAOの価格を崩壊させ、攻撃者とは無関係な便乗のアービトラージ業者もこの価格崩壊の中で価値を持ち去りました。オンチェーンで直接確認された抽出額は約$1.36Mで、主に20.83 BTCでしたが、攻撃者に残存するCACAOおよびトレードアカウントの残高を計算に入れると、公式の推定額である約$1.76Mとなります。
結論
このインシデントは、単一のバグではなく、会計処理と状態検証における一連の欠陥によって引き起こされました。単独では致命的なステップはありませんでしたが、それらが組み合わさることで、巧妙に作成されたデポジットが、薄いプールの記録上の残高を裏付けのない価値に変えることができました。核心的な要件はアトミック性と限定された信頼です: 会計処理の変更とそれに資金を供与するはずの転送は、成功するか失敗するかを共にすべきであり、失敗したリカバリーパスが完了とマークされることは決してあってはならず、低流動性プールに対する評価には上限を設けるべきです。また、共有のインバウンド状態は、同じトランザクション内の後のメッセージによって黙って上書きされるべきではありません。
参考文献
BlockSecについて
BlockSecは、フルスタックのブロックチェーンセキュリティおよび暗号資産コンプライアンスプロバイダーです。私たちは、プロトコルおよびプラットフォームのライフサイクル全体において、コード監査(スマートコントラクト、ブロックチェーン、ウォレットを含む)の実施、攻撃のリアルタイムでの阻止、インシデントの分析、不正資金の追跡、AML/CFT義務の遵守を、お客様が行えるよう支援するプロダクトおよびサービスを構築しています。
BlockSecは、権威ある会議において複数のブロックチェーンセキュリティ論文を発表し、複数のDeFiアプリケーションのゼロデイ攻撃を報告し、複数のハッキングをブロックして2000万ドル以上を救済し、数十億ドル相当の暗号資産を保護してきました。
-
公式ウェブサイト: https://blocksec.com/
-
公式Twitterアカウント: https://twitter.com/BlockSecTeam



