牙を剥くメモリプール:AMMにおける「サンドイッチ攻撃」の真実と防衛術
やあ。現場でコードを書き、ネットワークのパケットを追いかけ、時にはインシデント対応で徹夜明けのコーヒーを啜っている君たちへ。
今日は、Web3の世界、特にAMM(自動マーケットメーカー)における「サンドイッチ攻撃」の話をしよう。IoTや制御システムのセキュリティを専門にしていると、時折「ブロックチェーンは改ざん不可能だから安全だ」という無邪気な声を聞くことがある。だが、それは大きな誤解だ。「トランザクションが確定する前の状態」は、極めて脆弱な無法地帯なのだ。
1. サンドイッチ攻撃とは何か:メモリプールの覗き見
サンドイッチ攻撃は、典型的なフロントランニングの一種だ。仕組みは恐ろしいほどシンプルだが、残酷なまでに効率的だ。
1. 監視: 攻撃者はメモリプール(Mempool)を監視し、誰かが大規模な買い注文(Swap)を出そうとしているのを見つける。
2. 先行挿入(Front-running): 攻撃者は、ターゲットよりも高いガス代(手数料)を設定し、ターゲットの直前に自分の買い注文をブロックに滑り込ませる。これにより、価格を意図的に釣り上げる。
3. ターゲットの約定: ターゲットの注文が実行される。攻撃者が釣り上げた価格で買わされるため、ターゲットは甚大なスリッページを被る。
4. 後追い売り(Back-running): 攻撃者は直後に売り注文を入れ、価格を元に戻して利益を確定させる。
これが、君たちの書いたスマートコントラクト、あるいは君たちのクライアントが使うWeb3フロントエンドで起きている「現実」だ。
2. なぜスリッページ許容値が「命綱」なのか
多くのエンジニアが犯す最大のミスは、フロントエンドでスリッページ許容値(Slippage Tolerance)をハードコーディングするか、あるいは過剰に大きな値(デフォルトで5%など)を放置することだ。
スリッページ許容値とは、「この価格変動までは受け入れる」という防衛ラインだ。これが甘いと、サンドイッチ攻撃者は君のユーザーの資産を、この「許容範囲」ギリギリまで掠め取っていく。
3. 実装の鉄則:セキュアなコントラクト呼び出し
スマートコントラクトを直接叩くバックエンド(Node.jsやPython)を構築しているなら、必ずamountOutMin(最低限受け取るトークン量)を厳密に計算して渡さなければならない。
以下は、Ethers.jsを用いた実務的な実装サンプルだ。
/**
* セキュアなSwap実行のためのパラメータ計算
* @param {object} routerContract - Uniswap V2等のルーターコントラクトインスタンス
* @param {string} amountIn - 投入するトークン量
* @param {string} path - トークンパス
* @param {number} slippage - スリッページ許容値(例: 0.005 = 0.5%)
*/
async function getSecureSwapParams(routerContract, amountIn, path, slippage = 0.005) {
// 1. まず、現在のレートで期待される出力額を取得する
const amounts = await routerContract.getAmountsOut(amountIn, path);
const expectedAmountOut = amounts[amounts.length - 1];
// 2. スリッページを適用し、最低受取額を計算する
// BigNumberを使用して精度を保つのが鉄則
const slippageFactor = 1 - slippage;
const minAmountOut = expectedAmountOut.mul(Math.floor(slippageFactor * 10000)).div(10000);
console.log(`期待受取額: ${expectedAmountOut.toString()}`);
console.log(`最低受取額(防衛ライン): ${minAmountOut.toString()}`);
return minAmountOut;
}
// 実行時:この minAmountOut を必ずコントラクトの引数に渡すこと
// await router.swapExactTokensForTokens(amountIn, minAmountOut, path, ...)
4. 現場で防ぐ:エンジニアが今すぐやるべきこと
コードの実装だけでは足りない。インフラエンジニアとしての視点も必要だ。
- フロントエンドのUIバリデーション: Webアプリ側でユーザーに「スリッページ設定」を明示的に行わせ、デフォルト値を0.5%以下に制限せよ。3%や5%を推奨設定にするのは論外だ。
- プライベートRPCの利用: メモリプールへの露出を避けるため、Flashbots ProtectのようなプライベートRPCエンドポイントを利用せよ。これにより、トランザクションが一般のメモリプールに晒されるのを防ぎ、サンドイッチ攻撃の対象外とすることができる。
- WAFによる不審なリクエストの遮断: もし君がDAppのバックエンドを運用しているなら、特定のIPから短時間に異常なガス代のトランザクション生成リクエストが来ていないか監視せよ。
最後に:セキュリティは「諦めない」こと
サンドイッチ攻撃は、プロトコルの仕様を悪用した「仕様通りの挙動」に見えるかもしれない。しかし、それはユーザーの資産を奪う明白な攻撃だ。
「面倒だから」「動けばいいから」という妥協が、インシデントの温床になる。君たちが書くその一行のコード、その設定値一つが、ユーザーの資産を守る最後の砦だということを忘れないでほしい。
技術は常に進化する。だが、攻撃者の視点を持ち、常に最悪のケースを想定して実装するその姿勢こそが、君を最強のエンジニアにする。次のコードレビューでは、amountOutMinが適切に計算されているか、真っ先にチェックするように。
健闘を祈る。
コメント