過去1週間(2026/03/09 - 2026/03/15)に、BlockSecは8件の攻撃インシデントを検知・分析しました。推定損失の合計は約**$1.66M**です。以下の表に各インシデントの概要を示し、続く各節で詳細に分析します。
| 日付 | インシデント | 種類 | 推定損失 |
|---|---|---|---|
| 2026/03/09 | EtherFreakersインシデント | ビジネスロジックの欠陥 | 約$25K |
| 2026/03/10 | Alkemiインシデント | ビジネスロジックの欠陥 | 約$89K |
| 2026/03/10 | MTインシデント | ビジネスロジックの欠陥 | 約$242K |
| 2026/03/11 | AAVE清算インシデント | 設定ミス | 約$1.01M |
| 2026/03/11 | Planet Financeインシデント | ビジネスロジックの欠陥 | 約$10K |
| 2026/03/12 | AMインシデント | ビジネスロジックの欠陥 | 約$131K |
| 2026/03/12 | DBXenインシデント | ビジネスロジックの欠陥 | 約$149K |
| 2026/03/15 | Goose Financeインシデント | ビジネスロジックの欠陥 | 約$8K |
EtherFreakersインシデント
概要
2026年3月9日、Ethereum上のNFTゲームEtherFreakersが、誤った二重計上を原因とする攻撃を受け、約$25Kの損失が発生しました。ゲーム内の各NFTには、引き出し可能なETH残高(「energy」と呼ばれる)があります。ゲームの仕組みとして、プレイヤーはattack()を使い、あるNFTに別のNFTを捕獲させて対象のenergyを獲得できます。しかし、コントラクトは対象の残高を支払った後、会計処理を確定する前にNFTを移転します。このとき転送フックが、支払い前の古いデータを読み取り、その一部をグローバルな配当プールに戻します。その結果、新たなETHの裏付けがないままプールが膨らみます。攻撃者はこの捕獲の仕組みを繰り返してグローバルインデックスを押し上げ、複数のNFTから水増しされた残高を引き出しました。
背景
EtherFreakersは、各NFT(「Freaker」と呼ばれる)がenergyという引き出し可能なETH残高を持つ、オンチェーンのNFTゲームです。システムは配当プールのように機能します。特定の操作が行われると、ETHの一部がすべてのFreakerに比例配分されます。各Freakerが請求できるETHは、グローバル累積値freakerIndexと、トークンごとの持ち分の重みfortuneを組み合わせて追跡されます。
具体的な会計式は、energyOf = basic + (freakerIndex - index) * fortuneです。_dissipateEnergyIntoPool(amount)が実行されるとfreakerIndexが増加し、amountの80%がすべてのFreakerに、20%がクリエイターに分配されます。charge()による直接入金はbasicのみを増やし、freakerIndexには影響しません。したがって、freakerIndexの増加には、常に実際にシステムへ流入したEtherによる裏付けが必要です。対応するETHの流入なしにfreakerIndexが増加すると、Freakerはコントラクトが実際に保有する額を超えるEtherを償還できてしまいます。
脆弱性の分析
根本原因は、EtherFreakコントラクト(0x3A27...c0f33)における実行順序の誤りです。捕獲に成功すると、attack()関数は次の手順を順番に実行します。
- 237行目:
targetCharge(対象NFTのenergy全額)を、ETHの直接送金として防御側に支払います。この時点でenergyは消費されています。 - 240行目:
_transfer(defender, capturer, targetId)を呼び出してNFTを移転します。内部で_transfer()はERC-721フック_beforeTokenTransfer()を呼び出し、このフックがenergyOf(targetId)の0.1%を指定して_dissipateEnergyIntoPool()を呼び出します。これが1回目の呼び出しであり、手順5がまだ実行されていないため、古い値を読み取ります。 - 241行目:
_dissipateEnergyIntoPool(sourceSpent)を明示的に呼び出します。これが2回目の呼び出しで、通常のゲームロジックの一部です。 - 244~251行目:
sourceIdとtargetIdの両方のenergyBalancesを更新します。
バグは手順2にあります。energyBalances[targetId]がまだ更新されていないため、フックには支払い前の残高が見え、すでに消費されたenergyの一部を配当プールに投入してしまいます。手順1のETH直接支払いと手順2のプールへの投入が同じenergyを原資としているため、新たなETHの裏付けなしにfreakerIndexが膨らみます。


_dissipateEnergyIntoPool()が呼び出されるたびに、freakerIndexは増加します。

攻撃の分析
以下の分析は、トランザクション0x89e24d...9abd2942に基づきます。
-
手順1:
1,700WETHを借り入れます。 -
手順2: 攻撃者が管理するアドレスで、トークン
590とトークン591という新しいFreakerを2つミントします。 -
手順3: ゲームの
attack(590, 591)関数を繰り返し呼び出し、捕獲に成功する分岐で処理を進めます。 -
手順4: 成功するたびにトークン
591をヘルパーへ送り返し、同じペアを再利用します。 -
手順5: ループが成功するたびに、
freakerIndexがシステム内に実際に残るEtherの額を超えて膨らみます。 -
手順6: インデックスが十分に高くなったところで、以前から管理していたFreakerを一括で引き出します。トークンID
496から520までのそれぞれから、0.278052246002402082Etherを引き出します。 -
手順7: 引き出したEtherを
WETHにラップし、1,700WETHのフラッシュローンを返済して、約7.498WETHを利益として残します。
結論
根本原因は、attack()の捕獲成功時の処理にあります。EtherFreakersは、対象トークンのenergy状態を確定する前にtargetChargeを支払います。その後、_transfer()が_beforeTokenTransfer()を発火させ、支払い前の古いenergyOf(targetId)を読み取って、その一部をプールに分配します。これにより、新たなEtherの裏付けなしにfreakerIndexが増加し、同じ対象のenergyが支払いとプールへの投入の両方に計上されます。これはリエントランシーのバグではなく、ビジネスロジック上の残高水増しのバグです。
今後、同様のリスクを減らすには、以下が有効です。
-
同じトランザクション内で状態の確定処理が進行している間は、転送フック内で可変状態から経済的価値を再計算しないようにします。
-
転送フックが状態変数を読み取る場合は、実行順序が結果に影響しないようにします(例えば、フックの実行後ではなく実行前に状態を確定します)。
Alkemiインシデント
概要
2026年3月10日、Ethereum上のAlkemiプロトコルが攻撃を受け、約$89Kの損失が発生しました。根本原因は、会計処理の誤りとビジネスロジックの欠陥です。欠陥のある清算ロジックにより、誰でも同じトランザクション内で自分自身のポジションを清算して利益を得られます。さらに会計処理の誤りにより、清算時に攻撃者自身の担保から差し引かれるはずの額が上書きされ、攻撃者は本来のコストを負担せずに清算報酬を得られます。
背景
Alkemiはレンディングプロトコルです。借り手のポジションで担保が不足すると、誰でもliquidateBorrow()を呼び出して債務の一部を返済し、担保を割引価格で取得できます。過度の清算を防ぐため、プロトコルはトランザクションごとの返済可能額を、次の3つの値の最小値に制限しています。
- 借り手の現在の借入残高(
currentBorrowBalance_TargetUnderwaterAsset)。 - 清算時の割引を適用した後に、借り手の担保で賄える返済額の上限(
calculateDiscountedBorrowDenominatedCollateral())。 - アカウントを清算境界まで戻すのに必要な返済額(
calculateDiscountedRepayToEvenAmount())。市場のisSupportedが真の場合にのみ確認されます。


脆弱性の分析
根本原因は、Alkemiプロトコル(0x4822...a888)のビジネスロジックの欠陥と会計処理の誤りです。借り手に未返済の債務がある限りcurrentBorrowBalance_TargetUnderwaterAssetは必ず0より大きく、担保がある限りcalculateDiscountedBorrowDenominatedCollateral()の戻り値も必ず0より大きくなります。そのため、AlkemiEarnPublicプロトコルは、対象のローンを清算できるかどうかの判定を実質的にcalculateDiscountedRepayToEvenAmount()に依存しています。この関数では、清算が必要な債務額をaccountShortfall_TargetUserという変数に基づいて計算するべきです。
しかし、実際の実装では代わりにグローバル変数closeFactorMantissaを使って返済可能な債務額の上限を計算し、その値を返します。その結果、攻撃者は同じトランザクション内で借り入れ、直後に自分自身のポジションを清算できます。
さらに、liquidateBorrow()関数では、清算者と借り手が同じアドレスの場合、supplyBalance_TargetCollateralAssetとsupplyBalance_LiquidatorCollateralAssetが同じストレージスロットを指します。関数は同じ初期残高を基に「減額後の残高」と「報酬加算後の残高」を別々に計算し、その後、同じストレージスロットに順番に書き戻します。減額後の残高が先に書き込まれ、報酬加算後の残高が後から書き込まれるため、減額の効果が上書きされて失われ、報酬加算後の結果だけが残ります。これにより、攻撃者は利益をさらに増幅できます。
攻撃の分析
以下の分析は、トランザクション0xa170...6d9dに基づきます。
-
手順1: 攻撃者はBalancerから
51e18WETHのフラッシュローンを受けます。 -
手順2: 攻撃者は
51e18WETHをETHにアンラップし、Alkemiプロトコルに預け入れます。 -
手順3: 攻撃者はAlkemiから
39.5e18ETHを借り入れます。 -
手順4: 攻撃者は
39.5395e18ETHを使って自分自身のポジションを清算します。 -
手順5: 攻撃者はAlkemiから
93.5e18ETHを引き出します。 -
手順6: 攻撃者はフラッシュローンを返済し、
43.4e18ETHの利益を得ます。

結論
根本原因は、欠陥のある清算ロジックにより、攻撃者が同じトランザクション内で借り入れた後、自分自身のポジションを清算して利益を得られることです。誤った会計処理ロジックが、その利益をさらに増幅します。
今後、同様のリスクを減らすには、以下が有効です。
- 借り手と清算者の残高を更新する際は、残高を一時的なメモリ変数にコピーして別々に計算・書き戻すのではなく、ストレージ変数を直接操作するべきです。
MTインシデント
概要
2026年3月10日、BNB Chain上のデフレ型トークンMT Tokenが攻撃を受け、約$242Kの損失が発生しました。根本原因は、取引制限ロジックの欠陥と、特殊な転送条件の扱いにおける不整合です。デフレ期間中、コントラクトはプールのリザーブが固定のしきい値を超えると購入を制限します。しかし、ちょうど指定額(例えば2e17 MT)の転送は紹介者の紐付け操作として扱われるため、攻撃者は購入制限を回避して初期トークンを取得できます。さらに、制限ロジックは不完全な経路判定(isBuy)に依存しており、PairからRouterへの間接的なスワップ経路を対象にしていません。ホワイトリストのチェックも重要な検証を途中で打ち切ります。攻撃者は制限や手数料を発生させずにMTトークンを蓄積し、管理下の流動性操作と取引でpendingBurnAmountを操作して、トークン価格が人為的に高騰する異常な状態へプールを追い込みました。
背景
MT Tokenは、取引制限を組み込んだBNB Chain上のデフレ型トークンです。デフレ期間中、プール内のMTリザーブが21,000e18を超えると、コントラクトは購入をブロックします。リザーブがこのしきい値を下回るとデフレ期間が終了し、購入が再び可能になります。MT Tokenには紹介制度もあり、ちょうど2e17 MTまたは1e17 MTの転送は、通常の取引ではなく紹介者の紐付け操作として扱われます。
脆弱性の分析
根本原因は、MTコントラクト(0x037E...b449)の購入制限設計の欠陥です。通常、制限期間中に攻撃者が元手となるMTを取得することはできないはずです。しかし、コントラクトはちょうど2e17 MTの転送を購入ではなく紹介者の紐付け操作として扱います。そのため、攻撃者は購入制限を回避しながら2e17 MTを購入できます。

また、取引制限は購入をブロックするためにisBuy分岐に依存していますが、「PairからRouterへ」という経路には対応していません。RouterとPairはどちらもホワイトリスト上のアドレスであるため、この種の転送はホワイトリストのチェックで処理が終了し、購入制限ロジックには到達しません。攻撃者は購入先をRouterに指定し、その後、流動性を引き揚げてトークンを自身に戻すことでMTを取得できます。

攻撃の分析
以下の分析は、トランザクション0xfb57...fca6に基づきます。
-
手順1: 攻撃者は
~358,681e18WBNBをフラッシュローンで借り入れます。 -
手順2: 攻撃者は
2e17MTを購入し、購入制限を回避します。 -
手順3: 攻撃者は
4e12WBNBと2e17MTをPairに預けて流動性を追加します。この転送も、上述と同じ理由で手数料徴収ロジックを回避します。 -
手順4: 攻撃者はPairからRouter宛てに
~10,000,000e18MTトークンを購入し、購入制限と手数料徴収ロジックの両方を回避します。 -
手順5: 攻撃者は流動性ポジションの半分を引き揚げ、その過程でRouterが保有するすべての
MTトークンを取り出します。その後、回収したMTをWBNBに売却します。この手順で、pendingBurnAmountは約9,000,000e18に操作されました。
-
手順6: 攻撃者は
~10,000,000e18MTトークンを再び購入し、プールのMTリザーブをpendingBurnAmountより少ない~6,756,516e18まで減らします。
-
手順7: 攻撃者は流動性ポジションの残り半分を引き揚げ、購入した
MTトークンを取り出します。その後、distributeDailyRewards()を呼び出してプール内のMTをバーンします。その結果、MTリザーブは21,000e18まで減少します。
-
手順8: 攻撃者はすべての
MTを~1,198e18WBNBにスワップし、フラッシュローンを返済して利益を確定します。
結論
この攻撃は、取引制限の不備によって発生しました。攻撃者はちょうどBINDING_AMOUNT分のMTトークンを購入し、購入禁止を回避できました。MTトークンを取得した後は、まず流動性を追加し、次にRouter宛てにMTを購入し、最後に流動性を引き揚げてトークンを回収することで、手数料徴収ロジックと購入制限の両方を回避できました。その後、売却操作でpendingBurnAmountを積み上げ、バーンを実行してプールのリザーブを異常な状態に追い込みました。これにより、水増しされた価格でMTを売却して利益を得ました。
今後、同様のリスクを減らすには、以下が有効です。
- 転送の意味付けと取引ロジックを厳格に分離します。
AAVE清算インシデント
概要
2026年3月11日、AAVEではEthereum上で$21M規模の誤った清算が発生し、約$1.01Mの損失が生じました。根本原因はwstETHのオラクル価格の誤りで、もともと健全だったポジションが担保不足と判定されました。その結果、ユーザーのポジションが清算され、金銭的損失につながりました。
背景
AAVEは、wstETHのようなラップされた資産の価格算定にオラクルアダプターを使用します。アダプターのCAPO(Capped Price Oracle)は、ベースとなるETH/USD価格に換算比率(getRatio()、すなわち1 wstETHに相当するETHの量)を掛けて、wstETHの価格を算出します。比率の操作を防ぐため、CAPOはスナップショットに基づく増加率の上限を適用します。
maxRatio = snapshotRatio + maxGrowthPerSecond x (currentTime - snapshotTimestamp)
そして、価格算定時にgetRatio()の出力を上限で制限します(currentRatio > maxRatioの場合、maxRatioを使用します)。この仕組みにより、比率と、それに基づく価格の上昇幅に実質的な上限が設けられます。
脆弱性の分析
根本原因は、CAPOオラクルのアンカー設定(0xe1D9...61Ef)における時刻と比率の不整合です。スナップショットのタイムスタンプと比率は設定されていましたが、スナップショット比率が実際のwstETH/ETH比率より低く設定されていました。その結果、アダプターが計算したmaxRatioは実際の比率を下回り、getRatio()は低い値に制限され、wstETH/USDのオラクル価格が継続的に過小評価されました。この担保評価額の低下により、wstETHを担保に使用していたポジションのヘルスファクターが下がり、本来は健全なアカウントが誤って不健全と判定され、清算されました。


攻撃の分析
以下の分析は、トランザクション0x9064...8a9cに基づきます。
-
手順1: 清算者は
~6304e18WETHをフラッシュローンで借り入れ、借り手のポジションを清算します。 -
手順2: 清算者はフラッシュローンを返済し、清算を完了します。
結論
この清算は、オラクル価格の設定ミスにより、本来は健全な状態を維持するはずの借り手が誤って不健全な状態と判定され、ポジションの清算が発動したことで発生しました。
今後、同様のリスクを減らすには、以下が有効です。
-
重要なパラメータを更新する前に、毎回その正確性を確認します。
-
誤ったパラメータを拒否し、誤った設定が適用されないようにする検証チェックを実装に追加します。
Planet Financeインシデント
概要
2026年3月11日、BNB Chain上のPlanet Financeが攻撃を受け、推定約$10Kの損失が発生しました。根本原因は、プロトコルが借り手の記録上の借入残高の増加を誤って未払い利息とみなしたことです。これにより、攻撃者は借り入れと割引の精算を繰り返し発動させ、記録上の債務を過少にできました。
背景
Planet Financeは、借り手が利息割引を受けて返済できるレンディングプロトコルです。割引は段階制で、ユーザーがステーキングしたGAMMAと、その他の資産でステーキングした価値の比率によって決まります。この比率が高いほど、返済時の割引率も高くなります。割引は3段階で、0%(最小)から50%(最大)まであります。
脆弱性の分析
根本原因は、changeUserBorrowDiscount()で借り手の割引を精算する際、プロトコル(0x4c9E...F467)が、借り手の記録上の借入残高の増加を新たに発生した利息と誤認したことです。その結果、本来は発生済み利息にのみ適用される割引が、新たに借り入れた元本にも誤って適用され、借り手の記録上の債務が不当に減少しました。攻撃者はborrowの後にchangeUserBorrowDiscountを実行するループを繰り返して過大な割引を蓄積でき、オンチェーンに記録された債務を実際の借入額より継続的に低くし、最終的にその差額から利益を得られました。


攻撃の分析
以下の分析は、トランザクション0x5f45...5ec9に基づきます。
-
手順1: 攻撃者は
200,000e18USDTをフラッシュローンで借り入れます。 -
手順2: 攻撃者は
5,000e18USDTでWBNBを購入し、取得したWBNBを使って~8,726,524e18GAMMAを購入します。 -
手順3: 攻撃者はまず、取得したすべての
GAMMAをgGAMMA市場にステーキングします。次に残りのUSDTを担保として預け入れ、返済時の割引率を5%に引き上げて、その後の借り入れを可能にします。 -
手順4: 攻撃者は
borrowと、その後のupdateUserDiscountを繰り返し呼び出し、記録上の債務を継続的に減らします。
-
手順5: 最終的に攻撃者は債務を返済し、担保を償還して利益を確定します。
結論
このインシデントは、Planet FinanceのchangeUserBorrowDiscount()における割引精算ロジックの欠陥によって発生しました。借り手の記録上の借入残高の増加を新たに発生した利息と誤認し、その増分に利息割引を適用します。攻撃者はborrowに続けてupdateUserDiscountを繰り返し呼び出し、記録上の債務を過少にして、最終的に実際の債務より少ない額を返済することで利益を得られます。
今後、同様のリスクを減らすには、以下が有効です。
- レンディングプロトコル内で、利息と新規借入を区別します。
AMインシデント
概要
2026年3月12日、BNB Chain上のデフレ型トークンAM Tokenが攻撃を受け、推定約$131Kの損失が発生しました。AM Tokenには、売却のたびに流動性プールから追加でトークンをバーンし、総供給量を恒久的に減らすデフレの仕組みがあります。しかし、バーンはすぐには実行されません。代わりに、売却額の全額がtoBurnAmountとして記録され、実際のバーンは次の売却まで延期されます。この遅延により、記録から実行までの間に攻撃の余地が生まれます。攻撃者はその間にAMを買い戻し、プール内のAMリザーブをtoBurnAmountと同じ額まで減らせます。次の売却で延期されていたバーンが発動すると、AMリザーブがすべて消去され、価格が極端な水準に達します。これにより、攻撃者はAMを売却して利益を得られます。
背景
AM Tokenは、BNB Chain上のデフレ型トークンです。売却のたびに、コントラクトはスワップに関わるAMの額をtoBurnAmountとして記録し、次の売却時に、その記録額を流動性プールからバーンします。つまり、売却によって遅延バーンが発生し、プールのAMリザーブが減少します。また、バーンを実行する前に、プロトコルは蓄積されたtotalTokenFeeをUSDTにスワップし、手数料配分ロジックに従って分配します。
脆弱性の分析
根本原因は、トークン(0x27f9...213f)の売却ロジックが、スワップされたAMの全額をtoBurnAmountとして蓄積し、次の売却時にAM/USDTペアからトークンを除去してpair.sync()を呼び出し、リザーブを更新することで初めてバーンを実行することです。この設計により、攻撃者はプールのAMリザーブを操作してオンチェーン価格を歪め、裁定取引によって利益を得られます。


攻撃の分析
以下の分析は、トランザクション0xd0d1...f859に基づきます。
-
手順1: 攻撃者は
~27,265,119e18USDCと~361,710e18WBNBをフラッシュローンで借り入れ、それらを~100,423,811e18USDTにスワップします。 -
手順2: 攻撃者は
~5,062e18AMトークンをUSDTにスワップし、コントラクトに記録されるtoBurnAmountを~4,303e18に操作します。
-
手順3: 攻撃者は
USDTをAM Tokenにスワップし、プールのAMリザーブを~4,303e18まで減らします。
-
手順4: 攻撃者は
6 weiのAMをプールに転送し、売却経路のバーンロジックを発動させます。その結果、コントラクトはプール内のAM残高をすべてバーンし、AMリザーブを0まで減らします。注:プロトコルはバーン前に、蓄積された手数料をUSDTにスワップしようとします。この手数料換算の経路でも、売却分岐のバーンロジックが発動します。バーンが実行されてAMリザーブが0になると、手数料のスワップは失敗します。しかし、try/catchで囲まれているため、この失敗によってトランザクションは取り消されません。処理は続行され、手数料の累積値は0にリセットされます。
-
手順5: 攻撃者は
pool.sync()を呼び出し、残りのUSDTと1 weiのAMをプールに転送します。両方のトークンが同時に転送されたため、コントラクトはこれをaddLiquidityとして扱い、toBurnAmountは蓄積されません。AMリザーブは7に更新されます。

-
手順6: 攻撃者は残りの
AMトークンをUSDTにスワップします。このスワップ中、PairへのAM転送が売却経路のバーンロジックを発動させ、AMリザーブは1に減少します。さらに、手順4でtotalFeeAmountが0にリセットされていたため、手数料のUSDTへの換算は実行されません。その結果、攻撃者は人為的に高騰した価格でAMを売却できます。
-
手順7: 攻撃者はフラッシュローンを返済し、残った利益を確定します。
結論
このインシデントは、AM Tokenのバーンの仕組みに欠陥があったために発生しました。売却ごとに、スワップに関わるAMをtoBurnAmountとして蓄積し、次の売却時にpool.sync()を呼び出しながら、その額をAM/USDT Pairからバーンします。これにより、攻撃者はPairのAMリザーブを極端に少ない水準まで操作し、人為的に高騰した価格でAMを売却してUSDTを引き出せます。
今後、同様のリスクを減らすには、以下が有効です。
- トランザクションごとの最大バーン額を制限し、バーンを発動できる頻度にも制限を設けます。これにより、攻撃者が短時間でプールのトークンリザーブの大部分を消費するのを防ぎます。
DBXenインシデント
概要
2026年3月12日、EthereumとBNB Chain上のバーン報酬型プロトコルDBXenが攻撃を受け、合計で約$149Kの損失が発生しました。根本原因は、_msgSender()とmsg.senderの不整合です。forwarderを通じてburnBatch()を呼び出すと、バーンされたXENの額は_msgSender()(攻撃者が制御可能)のアドレスに記録されますが、サイクル記録はmsg.sender(forwarder)のアドレスで更新されます。この分離により、攻撃者は更新されていない古いサイクル記録に基づいて報酬と手数料を請求でき、異常に大きな支払いが発生します。
背景
DBXenは、トークンをバーンして報酬を得るプロトコルです。ユーザーはXENトークンをバーンし、引き換えにDXN報酬と、蓄積されたプロトコル手数料の一部を受け取ります。中心となる仕組みはサイクル単位で動作します。ユーザーがburnBatch()を呼び出すと、次の2つが起こります。(1) バーンされたXENの額が、_msgSender()で識別される呼び出し元のアドレスに記録されます。(2) XENコントラクトがonTokenBurned()を介してDBXenをコールバックし、呼び出し元のサイクル記録(バーンサイクルとlastFeeUpdateCycle)を現在のサイクルに更新します。
報酬と手数料はupdateStats()で精算されます。報酬は、そのバーンサイクルでバーンされたXENの総量に占めるユーザーの割合に比例します。手数料は、ユーザーについて最後に記録されたサイクル以降に蓄積されたプロトコル手数料の累計に基づきます。どちらの計算も、ユーザーのサイクル記録が最新であることを前提としています。




脆弱性の分析
根本原因は、DBXenプロトコル(0xf5c8...2abd)のビジネスロジックの欠陥です。_msgSender()関数は、msg.senderがforwarderかどうかを確認します。該当する場合はcalldataの末尾20バイトを返しますが、この戻り値はforwarder経由の呼び出しでは任意に制御できます。一方、burnBatch()はmsg.senderが保有するXENを直接バーンします。そのため、攻撃者はforwarderを通じてburnBatch()を呼び出し、プロトコルにforwarder保有のXENをバーンさせ、forwarderのバーンサイクル記録と手数料更新サイクル記録の両方を現在のサイクルに更新させることができます。しかし同時に、プロトコルはバーンされたXENの額を_msgSender()に対応するアドレスに記録します。
その後、攻撃者はclaimFees()を呼び出し、updateStats()を実行させます。_msgSender()のアドレスに紐づくサイクル記録は一度も更新されていないため(バーンサイクルとlastFeeUpdateCycleはどちらも0のまま)、updateStats()は現在のサイクルの報酬を計算するとともに、サイクル0以降、つまりプロトコルの全履歴にわたって蓄積された手数料を計算します。攻撃者はclaimFees()とclaimRewards()を呼び出して利益を得ます。

攻撃の分析
以下の分析は、トランザクション0x914a5a...b808bc37に基づきます。
-
手順1: 攻撃者はまず
ForwarderコントラクトのregisterDomainSeparator()関数を呼び出し、その後のForwarder.execute()の呼び出しを可能にします。 -
手順2: 攻撃者はUniswap V2プールで
0.14e18ETHを13,900,000,000e18XENにスワップします。 -
手順3: 攻撃者は
13,900,000,000e18XENをForwarderコントラクトに転送します。 -
手順4: 攻撃者は
Forwarder.execute()を使い、Forwarderが保有する13,900,000,000e18XENをDBXenが使用することを承認します。 -
手順5: 攻撃者は
Forwarder.execute()を使ってDBXen.burnBatch()を呼び出し、13,900,000,000e18XENをバーンします。バーン額はアドレス0x425D3eC2DCeBE2c04bA1687504D43AFC6be7328dに記録されます。一方、バーンの実行中にXENがonTokenBurned()を介してDBXenをコールバックし、Forwarderに紐づくサイクル記録を更新します。
-
手順6: 攻撃者は
Forwarder.execute()を使ってDBXen.claimFees()を呼び出し、65.36e18ETHを取得します。 -
手順7: 攻撃者は
Forwarder.execute()を使ってDBXen.claimRewards()を呼び出し、2,305.4e18DXNをミントします。
結論
このインシデントの根本原因は、DBXenプロトコルが_msgSender()とmsg.senderを一貫せずに使用していたことです。この2つの値は異なる場合があるため、プロトコル内部の会計処理に不整合が生じ、攻撃者はその差異を利用して利益を得られました。
今後、同様のリスクを減らすには、以下が有効です。
- すべてのロジック経路で
_msgSender()を一貫して使用するか、msg.senderに依存する操作と_msgSender()に依存する会計処理が常に同じアドレスを参照するようにします。
Goose Financeインシデント
概要
2026年3月15日、BNB Chain上のイールドファーミングプロトコルGoose Financeが攻撃を受け、約$8Kの損失が発生しました。根本原因は、StrategyGooseEggにおけるシェア価格算定の処理順序の欠陥です。deposit()は、獲得した報酬を会計処理に反映する前にシェアをミントするため、シェア価格の算定に使う総資産の分母にはその報酬が含まれず、実際の価値より小さくなります。このため、預け入れたユーザーは本来より多くのシェアを受け取ります。withdraw()が呼び出されると報酬の回収が発動して総資産が増え、各シェアの価値が上がります。攻撃者は単一のトランザクション内で預け入れと引き出しを繰り返し、過大にミントされたシェアを修正後の高い価値で償還して、差額を利益として引き出しました。
背景
Goose FinanceはBNB Chain上のイールドファーミングプロトコルです。ユーザーの資金は保管庫からストラテジーに送られ、ストラテジーは資産をMasterChefにステーキングしてEGG報酬を得ます。
このインシデントに関係する構成要素は次のとおりです。
-
VaultChef(0x3f64...): ユーザーのポジションを追跡し、資金をStrategyGooseEggに転送します。 -
StrategyGooseEgg(0x0980...):sharesTotalとwantLockedTotalを使ってストラテジー単位の会計処理を管理します。 -
MasterChef(0xe70e...): ステーキングされた資産を受け取り、EGG報酬を支払います。 -
WrappedEgg(0xb815...): ステーキング用にEGGを1:1でWEGGにラップします。
運用上、預け入れ資金はVaultChefからStrategyGooseEggへ送られ、その後MasterChefにステーキングされます。引き出しはVaultChefから開始され、ストラテジーによって実行されます。
会計処理における重要な前提は、シェア価格が算定時点のストラテジーの総資産(ステーキングされた元本と、ストラテジーがすでに保有している未運用の報酬)を反映することです。しかしStrategyGooseEggでは、deposit()が_farm()によって未運用資産をwantLockedTotalに反映する前にシェアをミントし、withdraw()はMasterChefからの報酬回収を発動させる場合があります。この順序が、以下で分析する脆弱性の基になっています。


脆弱性の分析
根本原因は、StrategyGooseEgg(0x0980...b26b)における、報酬の回収とシェア価格算定の間の会計処理の不整合です。
StrategyGooseEggでは、シェア価格の算定にwantLockedTotalを分母として使用します。式はshares = deposit * sharesTotal / wantLockedTotalです。公平な算定のためには、wantLockedTotalに、コントラクト内に残る未運用のEGG報酬を含め、ストラテジーが実際に保有するすべての資産が反映されている必要があります。しかし、deposit()は_farm()が未運用の報酬をwantLockedTotalに反映する前にシェアをミントします。つまり、分母には未計上の報酬が含まれず、実際の総資産より小さいため、預け入れたユーザーは本来より多くのシェアを受け取ります。
さらに、withdraw()はMasterChef.withdraw()を呼び出し、ステーキングされた元本と未払いのEGG報酬をストラテジーに戻します。ストラテジーの会計処理では、要求された_wantAmtだけをwantLockedTotalから差し引くため、回収した報酬はwantLockedTotalに反映されないまま、ストラテジーの残高に残ります。これにより、実際の保有資産と記録上のwantLockedTotalの差が広がり、その後のdeposit()におけるシェア価格算定はさらに不正確になります。
攻撃の分析
以下の分析は、トランザクション0x86efdf...ce316223に基づきます。
-
手順1: 攻撃者は2つのPancakeペアから
EGGをフラッシュローンで借り入れます。 -
手順2: 最初に
VaultChef/StrategyGooseEggへ預け入れます(10,170,000e18EGG)。 -
手順3: 最初の引き出し(
12,593,884e18EGG)によってMasterChefから報酬が回収されます。359,561e18EGGがStrategyGooseEggに転送され、未運用・未計上の価値として残ります(R > 0)。 -
手順4: 2回目の預け入れでは、引き出した資金(
12,593,884e18EGG)を再利用します。未運用の価値が精算される前にシェア価格が算定されるため、ここでシェアが過大にミントされます。 -
手順5: 2回目の引き出し(
12,826,027e18EGG)で、過大にミントされたシェアから利益を実現します(つまり、手順4の預け入れ額より232,143EGG多い額です)。 -
手順6: 攻撃者はフラッシュスワップを返済し、差額の純利益を得ます。
結論
この攻撃は、StrategyGooseEggにおけるシェア価格算定の処理順序の欠陥に起因します。deposit()は_farm()がwantLockedTotalを更新する前にシェアをミントする一方、withdraw()はMasterChefからの報酬回収を発動でき、その報酬は一時的に未運用・未計上のまま残ります。これにより、預け入れ時には古い分母に基づいてシェアがミントされ、後に更新された資産額に基づいて引き出せます。
今後、同様のリスクを減らすには、以下が有効です。
-
シェアのミントとバーンのいずれについても、計算前に報酬を精算し、会計処理を更新します。
-
シェア価格を算定するその時点で、単一の
totalAssets(staked + idle)を基準にします。 -
未運用の報酬がゼロでない条件下で、
shares_minted <= D * S / (A + R)について不変条件テストを追加します。
BlockSecについて
BlockSecは、ブロックチェーンセキュリティと暗号資産コンプライアンスを包括的に提供する企業です。プロトコルやプラットフォームのライフサイクル全体を通じて、コード監査(スマートコントラクト、ブロックチェーン、ウォレットを含む)、リアルタイムの攻撃阻止、インシデント分析、不正資金の追跡、AML/CFT義務への対応を支援する製品とサービスを開発しています。
BlockSecは、権威ある学会でブロックチェーンセキュリティに関する論文を複数発表し、DeFiアプリケーションへの複数のゼロデイ攻撃を報告してきました。また、複数のハッキングを阻止して2,000万ドルを超える資産を救済し、数十億ドル規模の暗号資産の安全を確保してきました。
-
公式ウェブサイト: https://blocksec.com/
-
公式Twitterアカウント: https://twitter.com/BlockSecTeam
-
🔗 BlockSec監査サービス : お問い合わせ



