過去1週間(2026/06/15 - 2026/06/21)に、合計約1,830万ドルの損失をもたらした注目すべきセキュリティインシデントを3件観測しました。
| 日付 | インシデント | 種別 | 推定損失 |
|---|---|---|---|
| 2026/06/18 | Aztec | 不適切なパブリック入力バインディング | ~$2.2M |
| 2026/06/20 | LABUBU Token | 設定ミス | ~$1.1M |
| 2026/06/20 | jaredFromSubway | 不適切な承認管理 | ~$15M |
- Aztec: 3日以内にプロトコルが2度目の攻撃を受け、今回はエスケープハッチ回路を通じて悪用されたことで、ZK証明バインディングの繰り返し問題が浮き彫りになったため選出。
- jaredFromSubway: MEVボットのコントラクトが、消費や残余承認額の取り消しを検証せずに信頼できないトークンコントラクトへの承認を付与し、攻撃者がそれを積み重ねて約1,500万ドルを引き出すことを可能にしたため選出。
Web3向け最高のセキュリティ監査機関
ローンチ前にデザイン、コード、ビジネスロジックを検証
今週のハイライト:jaredFromSubway
従来の承認悪用攻撃(攻撃者が信頼済みのDeFiコントラクトの脆弱性を悪用し、ユーザーがそのコントラクトに承認した資産を引き出す)とは異なり、今回の攻撃は逆方向を標的とした。MEVボットがアービトラージ操作の一環として、自身の資産に対する承認を信頼できないサードパーティコントラクトに対して能動的に付与していた。攻撃者はフェイクのスワッププールがリアルなSwapおよびSyncイベントを発行しながらフェイクトークンが付与された承認額を一切消費しないハニーポット的な偽の取引環境を構築し、外向きの承認を積み重ねてから一気に回収した。報告された総損失は約1,500万ドル。
2026年6月20日、EthereumのMEVボットオペレーターであるjaredFromSubwayは約1,500万ドルを失った [1]。オンチェーン分析に基づくと、根本原因はボットコントラクトにおける不適切な承認管理にある。承認がそれらを消費しない信頼できないラッパーコントラクトに付与され、攻撃者はこれらの未消費の承認額を積み重ねたうえで、単一のトランザクションでボットの実際の残高を引き出した。
背景
jaredFromSubwayはEthereumにおいてサンドイッチ攻撃やオンチェーンアービトラージを専門とする著名なMEVボットオペレーターである。被害を受けたコントラクト(0x1f2f...f387)はその運用ウォレットの1つであり、WETH、USDC、USDTの大きな運転資金残高を保有している。
この種のMEVボットは、オンチェーンに出現する任意の新しいトークンやプールと動的にやり取りしなければならない。ボットはメンプールを監視し、トランザクションをシミュレートし、アービトラージの機会を捉えるためにトークンインタラクションを自動的に承認する。この運用モデルは、トークンが期待どおりに動作するという前提に依存している。すなわち、スワップが実行されると、トークンコントラクトはtransferFromを呼び出すことで付与された承認額を消費する。
脆弱性の分析
根本原因は、信頼できないコントラクトとやり取りする際のMEVボットの不適切な承認管理にある。
ボットはUniswapのプールやルーターを介してさまざまなアービトラージパスを実行する。ほとんどのインタラクションでは、ボットはtransferを通じて直接プールにトークンを送り込む。この場合、ボット自身がmsg.senderとなるため承認は不要だ。しかし、ラッパー型トークンコントラクトとのインタラクションはプルモデルに従う。ボットがwrapper.wrapTo()を呼び出すと、その呼び出しの内部でラッパーコントラクトがrealToken.transferFrom(bot, wrapper, amount)を呼び出してボットの実トークンを引き出す。transferFromの際のmsg.senderはボットではなくラッパーコントラクトであるため、事前のapproveが必要となる。
<real_token>.approve(tokenContract, amount)— ラッパーコントラクトに対して実トークンの承認額を付与するtokenContract.wrapTo()→ プール間のマルチホップswap()→tokenContract.unwrap()— 実トークンをラップし、プールを通じてルーティングし、実トークンにアンラップして戻す
ボットは、正常に動作するラッパーコントラクトがそうするように、wrapTo()がtransferFromを通じて承認額を消費すると想定していた。しかしボットは、操作後に承認額が実際に消費されたかどうかを検証せず、残余の承認額も取り消さなかった。wrapTo()がtransferFromを呼び出さない場合、付与された承認額は操作後も完全に残存し、持続的な攻撃対象となる。そのような承認額を保持するコントラクトはいつでもtransferFromを呼び出してボットの実資産を移動させることができる。
攻撃の分析
オンチェーンの再構成に基づくと、攻撃者は上述の脆弱性を悪用するために3つのコンポーネントからなる偽の取引環境を構築した。
-
フェイクラッパートークン: 各フェイクトークンは実トークンの名前を使用しつつ、シンボルに
fを接頭辞として付けていた(例:USDCに対してnameUSD Coin、symbolfUSDC)。正規のラッパーを模倣するためにwrapTo()とunwrap()を実装し、さらに未消費の承認額をtransferFromで引き出す攻撃者限定のwithdraw()関数も備えていた。 -
フェイクスワッププール: 攻撃者は自己デプロイしたファクトリーを通じて約44のUniswap V2スタイルのプールをデプロイした。これらのプールはフェイクトークン同士をペアにして説得力のあるスワップルートを形成した。
swap()が呼び出されると、プールは正規の取引と見分けのつかないリアルなSyncおよびSwapイベントを発行した。 -
攻撃者が細工した利益:
unwrap()の際、フェイクトークンはtransferを通じてボットに少量の実トークンを送り返した。ボットは実際の利益を受け取ったが、それは市場アービトラージで得たものではなく攻撃者によって意図的に細工されたものだった。
攻撃者は外部コントラクトのブロックごとのgetStatus()スイッチを通じてこれらのコンポーネントを制御していた。getStatus()は、アクティベーショントランザクション(_getStatus = block.numberを設定する)と同一ブロックで呼び出された場合に1を返し、それ以外の場合は0を返した。getStatus() == 0の場合、wrapTo()は通常通りtransferFromを呼び出し承認額が消費された。getStatus() == 1の場合、wrapTo()はtransferFromをスキップし承認額が消費されなかった一方で、unwrap()は攻撃者が細工したトークンをボットに返した。承認額を積み重ねたいときは、攻撃者はビルダー賄賂を使ってアクティベーショントランザクションをボットのトランザクションと同一ブロックに配置していたものと思われる。
攻撃は3つのフェーズに分けて実行された。
フェーズ1:攻撃インフラのデプロイ
-
ステップ1: 攻撃者はブロック25354424から25354519にかけてインフラを構築した。これには、フェイクトークンファクトリーコントラクト(0x81f2...0091)のデプロイ、自己デプロイしたファクトリーを通じた約44のフェイクUniswap V2プールの作成、
swap()呼び出しが成功するようプールへの初期トークン残高の供給、およびガスとビルダー賄賂のためのハーベストコントラクト(0xb84d...df52)への0.01 ETHの送金が含まれた。 -
ステップ2: 攻撃者はCREATE2を使ってフェイクラッパートークンを大量生成した。各トークンは実トークンを模倣し(実名を使用しつつシンボルに
fを接頭辞として付ける)、攻撃者限定のwithdraw()関数を持っていた。CREATE2により決定論的なアドレスが得られ、ハーベストコントラクトがそれらをループ処理できた。
フェーズ2:信頼の構築と承認の積み重ね
-
ステップ3(初期の信頼構築): 最初期のトランザクション(例:ブロック25354425の0x542d...362b)では、フェイクトークンに
getStatus()スイッチはなく、wrapTo()はtransferFromを直接呼び出して承認額を消費した。ボットは承認し、ラップし、スワップし、アンラップし、通常通り利益を得た。これによりフェイクトークンが収益性の高い取引機会として確立された。 -
ステップ4(信頼の継続): その後のトランザクション(例:0x085e...37e51)では、
getStatus()スイッチはデプロイされていたが0を返した(アクティベーションとは異なるブロック)。wrapTo()は依然としてtransferFromを呼び出し承認額を消費した。ボットは利益を上げ続けインタラクションを継続した。 -
ステップ5(承認の積み重ね): ブロック25360519の0x8560...1915を起点に、攻撃者はビルダー賄賂を通じてアクティベーショントランザクションをボットのトランザクションと同一ブロックに配置し、
getStatus()が1を返すようにした。このモードではwrapTo()はtransferFromをスキップし承認額は消費されなかったが、unwrap()は依然としてボットに少量の実トークンを送り返した。ボットは収益性の高い操作と判断して承認をそのままにした。約600ブロック(約13トランザクション)にわたり、ボットはWETH、USDC、USDTでこのパターンを繰り返し、3つの実資産すべてで未消費の承認額を積み重ねた。
フェーズ3:回収
- ステップ6: 攻撃者はハーベストトランザクション0x2be870...cf3e65ですべてのフェイクトークンの
withdraw()を呼び出し、未消費の承認額を利用してtransferFromを呼び出すことでボットの実残高を攻撃者に移動させた。ブロックへの組み込みを確実にするために0.01 ETHのビルダー賄賂も含まれていた。このハーベストにより、被害コントラクト単体から1,474.58WETH+ 2,870,573USDC+ 2,035,760USDT(約750万ドル)が引き出された。
特定された攻撃トランザクションによる損失は約750万ドルであり、jaredFromSubwayの声明 [1] に基づく総損失は約1,500万ドルとされている。
まとめ
今回のインシデントの根本原因は、信頼できないコントラクトとやり取りする際のMEVボットの不適切な承認管理にあった。攻撃者が信頼済みのDeFiコントラクトの脆弱性を悪用してユーザーが承認した資産を引き出す従来の承認悪用攻撃とは異なり、今回の攻撃は逆方向に機能した。ボットがアービトラージ操作の一環として自身の資産を信頼できないサードパーティコントラクトに能動的に承認した。攻撃者はこれらの外向きで未消費の承認を積み重ね、単一のトランザクションで回収した。
将来的に同様のリスクを低減するために、信頼できないトークンコントラクトとやり取りするボットコントラクトは、各操作後に承認が消費されたことを検証し、残余の承認額を取り消すべきである。一見成功したように見える取引後の未消費の承認額は、悪意あるトークン動作の強力なシグナルである。
今週のその他のインシデント
Aztec
2026年6月18日、AztecのエスケープハッチZK回路における等値制約の欠如により、攻撃者はEthereumのレガシーRollupProcessorコントラクトから約220万ドル(1,158 ETH、150K DAI、約0.47 renBTC)を引き出すことに成功した [2], [3]。これは3日以内に発生した2度目のAztec攻撃であり(最初の攻撃は前回のレポートで取り上げたアップグレード済みRollupProcessorV3を標的とした)、関連するZK回路パブリック入力バインディングのバグによるものだ。
背景
AztecのレガシーRollupProcessorにはescapeHatch関数が含まれている。これはロールアップオペレーターが処理を停止した際に誰でも単一トランザクションの証明を提出できる安全機構だ。processRollup(認可されたプロバイダーを必要とする)とは異なり、エスケープハッチはブロック番号に基づく定期的なウィンドウで開放され、誰でも呼び出すことができる。
function escapeHatch(
bytes calldata proofData,
bytes calldata signatures,
bytes calldata viewingKeys
) external override whenNotPaused {
(bool isOpen, ) = getEscapeHatchStatus();
require(isOpen, 'Rollup Processor: ESCAPE_BLOCK_RANGE_INCORRECT');
processRollupProof(proofData, signatures, viewingKeys);
}
エスケープハッチは専用のZK回路(escape_hatch_circuit)を使用し、ジョインスプリットトランザクションを処理する。マークルツリーから入力ノートを消費し、出力ノートを作成する。回路はマークルメンバーシップにold_data_rootを使用して入力ノートが現在のデータツリーに存在することを検証し、その後L1コントラクトがオンチェーン状態と照合するためのパブリック入力として同じルートを公開しなければならない。
脆弱性の分析
脆弱性はエスケープハッチ回路(escape_hatch_circuit.cpp)に存在する。old_data_rootの値が、それらを結びつける等値制約なしに2つの独立したウィットネスに変換されている。
最初のウィットネス(33行目)はジョインスプリット回路コンポーネントに渡され、入力ノートがデータツリーに存在することを検証するためのマークルメンバーシップ証明に使用される。
join_split_inputs inputs = {
// ...
witness_ct(&composer, tx.js_tx.old_data_root), // 33行目:最初のウィットネス
// ...
};
auto outputs = join_split_circuit_component(composer, inputs);
2番目のウィットネス(50行目)は独立して作成されパブリック入力として公開され(88行目)、SolidityコントラクトがオンチェーンのデータルートとEの照合に使用する。
auto old_data_root = field_ct(witness_ct(&composer, tx.js_tx.old_data_root)); // 50行目:2番目のウィットネス
// ...
composer.set_public_input(old_data_root.witness_index); // 88行目:パブリック入力として公開
ZK回路において、各witness_ctの呼び出しは独立した変数を作成する。33行目と50行目の間に明示的なassert_equalがなければ、プロバーはこれら2つのウィットネスに異なる値を割り当てることができる。Solidity側では、validateMerkleRootsは50行目のパブリック入力のみを使用してrequire(oldDataRoot == dataRoot)を確認するため、33行目で使用された値は見えない。
同じバインディング解除のパターンは
input_ownerとoutput_ownerにも存在する。これらの値は38〜39行目でウィットネスされ(所有権検証のためにjoin_split_circuit_componentに渡される)、さらに111〜112行目で独立したパブリックウィットネスとして再び公開される。ただし、このギャップに対する実用的な攻撃経路は特定されていない。
攻撃の分析
エスケープハッチ回路はaztec-connectコードベースから削除されていた [4] が、デプロイ済みの検証コントラクトには依然としてEscapeHatchVk検証キーが含まれているため、脆弱な回路で生成された証明は依然としてオンチェーン検証を通過できる。攻撃時点で、コントラクトは142日間沈黙しており、エスケープハッチウィンドウは開いていた。攻撃者のアドレスはUnion Chainを経由して攻撃のわずか14時間前に作成されていた [2]。攻撃は3つのコアステップで構成されている。
-
ステップ1: 攻撃者は任意の価値を持つ自己所有のノートを含むフェイクマークルツリーを構築した。これらのノートは実際のオンチェーンデータツリー(すべての有効なノートを格納するマークルツリー)には存在しなかった。
-
ステップ2: 攻撃者は上述のバインドされていないウィットネスを悪用してエスケープハッチ証明を生成した。33行目のウィットネス(ジョインスプリットコンポーネントのマークルメンバーシップに使用)をフェイクマークルルートに設定した(作成されたノートがフェイクツリーに存在するためメンバーシップチェックは通過し、攻撃者が署名鍵を保持しているため所有権チェックも通過した)。50行目のウィットネス(Solidityが確認するパブリック入力として公開)をリアルなオンチェーンのデータルートに設定した(この値がコントラクトに格納されたルートと一致するため、Solidityの
require(oldDataRoot == dataRoot)チェックが通過した)。 -
ステップ3: 回路とSolidityの両方のチェックが満たされたため、証明の検証が成功した。コントラクトはエスケープハッチトランザクションを正当なものとして処理し、資金を放出した。
攻撃者はこのプロセスを3つのトランザクション(0x9e1d6a...6b03ca、0xab306c...59c2b5、0x5c196c...4705c3)にわたって繰り返し、異なる資産を標的にして、それぞれ1,158 ETH、150K DAI、約0.47 renBTCを引き出し、合計約220万ドルとなった。
まとめ
今回のインシデントの根本原因は、エスケープハッチ回路におけるold_data_rootの2つのウィットネス間の等値制約の欠如だった。一方のウィットネスはジョインスプリットコンポーネント内部のプライベートノートメンバーシップ検証に使用され、もう一方はSolidityが確認するパブリック入力として公開された。それらを結びつける制約がなかったため、攻撃者はフェイクマークルツリーに対して作成したノートの所有権を証明しつつ、L1コントラクトには有効なオンチェーンルートを提示することができた。特筆すべきは、脆弱な回路をソースから削除してもデプロイ済みの検証コントラクトは無効化されなかったという点だ。レガシーRollupProcessor上のescapeHatch関数は、そのブロック番号ウィンドウが開いている間はいつでも呼び出し可能なままである。
将来的に同様のリスクを低減するために、同一の論理値がZK回路内の複数の箇所に現れる場合、すべてのインスタンスが等しいと明示的に制約されなければならない。同一の値に対して独立したwitness_ctを呼び出すことはバインディングギャップとなる。回路の監査では、すべてのパブリック入力がそれが表す回路内部の値に確実にバインドされているかを体系的に検証すべきである。
参考文献
- [1] jaredFromSubway公式声明(約1,500万ドルの損失)
- [2] Phalconアラート:Aztecエスケープハッチ攻撃
- [3] Aztec Labs公式声明
- [4] エスケープハッチ回路の削除、aztec-connectコミット8c3953a
BlockSecについて
BlockSecはフルスタックのブロックチェーンセキュリティおよび暗号資産コンプライアンスプロバイダーです。コード監査(スマートコントラクト、ブロックチェーン、ウォレットを含む)、攻撃のリアルタイム遮断、インシデント分析、不正資金の追跡、AML/CFT義務の遵守など、プロトコルやプラットフォームのライフサイクル全体にわたる製品とサービスを提供しています。
BlockSecは著名な学術カンファレンスで複数のブロックチェーンセキュリティ論文を発表し、DeFiアプリケーションに対するゼロデイ攻撃を複数報告し、2,000万ドル以上を救済するために複数のハッキングをブロックし、数十億ドル相当の暗号資産を守ってきました。
-
公式ウェブサイト: https://blocksec.com/
-
公式Twitterアカウント: https://twitter.com/BlockSecTeam



