WebSocketの暗部:Origin検証の虚構と「見えない」セッションジャック
多くのアーキテクトが誤解していることがある。WebSocketは「ただのTCPの延長線上にあるHTTPアップグレード」ではない。それは、一度確立されればレイヤ7の制約をかなぐり捨て、恒久的なデータパイプラインとしてサーバーの心臓部へ直結する「裏口」だ。
HTTPのステートレスな世界から、WebSocketのステートフルな世界へ移行する際、多くのエンジニアが「認証はハンドシェイク時に済ませればいい」と判断する。だが、これが脆弱性の温床になる。今日は、その盲点を突き、どう防衛すべきかという深淵の話をしよう。
—
1. Origin検証という「ザル」の正体
WebSocketのハンドシェイクにおいて、ブラウザは Origin ヘッダーを送信する。多くのWebサーバーやライブラリは、この値を検証することでクロスサイト・WebSocket・ハイジャック(CSWSH)を防ごうとする。
しかし、攻撃者は Origin ヘッダーをいじれるわけではない。彼らが狙うのは「検証ロジックの不備」だ。
攻撃者の視点:ホワイトリストの甘い罠
よくある脆弱な実装例を見てみよう。
// 脆弱なOrigin検証の例(Node.js/wsライブラリ想定)
const wss = new WebSocket.Server({
verifyClient: (info) => {
// 悪い例:indexOfで前方一致をチェックしている
// これでは "https://trusted-app.com.attacker.com" を許容してしまう
return info.origin.indexOf('https://trusted-app.com') === 0;
}
});
この実装では、攻撃者が trusted-app.com.attacker.com というドメインを取得し、そこから攻撃用のJavaScriptをロードすれば、いとも簡単に Origin チェックをバイパスできる。
防衛の鉄則:
Origin 検証は、必ず「完全一致」で行うこと。また、サブドメインの動的生成が許容される設計なら、正規表現による厳密なバリデーションか、あるいはそもそも Origin ヘッダーを信頼しないアーキテクチャ(後述のトークンベース認証)を採用すべきだ。
—
2. ハンドシェイク後の「認証の剥落」
WebSocket通信において、最も深刻なミスは「ハンドシェイク時のCookie認証のみに頼ること」だ。
WebSocketは一度接続が確立されると、HTTPのCookie認証が継続して適用されるわけではない。ハンドシェイクが完了した瞬間に、その接続は「独立したパイプ」となる。もしアプリケーションがメッセージのたびに認証トークンを再検証していない場合、CSRFやCSWSHによって確立されたセッションが、そのまま攻撃者の意のままに操作されることになる。
アーキテクチャの解法:メッセージごとの署名
理想的な設計は、WebSocketの各フレーム(メッセージ)に認証トークン(JWT等)を含めることだ。
// クライアント側:メッセージ送信時のトークン付与
const socket = new WebSocket('wss://api.example.com');
socket.send(JSON.stringify({
type: 'TRADE_EXECUTION',
token: 'eyJhbGciOiJIUzI1NiIsInR5...', // 毎回、署名付きトークンを検証させる
payload: { amount: 1000, asset: 'BTC' }
}));
サーバー側では、この token をメッセージを受け取るたびに解読・検証する。これにより、たとえセッションがハイジャックされても、攻撃者が有効なトークンを生成できない限り、コマンドは拒絶される。
—
3. 生成AI時代の新たな脅威:プロンプトインジェクションとWebSocket
近年、WebSocket経由でLLM(大規模言語モデル)のストリーミングレスポンスを受けるアーキテクチャが増えている。ここで注意すべきは、WebSocketメッセージが「AIのプロンプトに対するガードレイルをすり抜ける経路」になり得ることだ。
クライアントから送られてきたWebSocketメッセージを、そのままLLMのコンテキストに注入していないだろうか?もしそうなら、それはSQLインジェクションの現代版、すなわち「プロンプトインジェクション」を広大な攻撃面に晒しているに等しい。
対策のアーキテクチャ:
1. 入力の正規化: WebSocket経由のメッセージを、一旦「JSONスキーマ検証」に通す。
2. 命令分離: ユーザー入力とシステムプロンプトを、データ構造レベルで厳格に分離する(role: "user" のようなメタデータ管理)。
3. 出力のサニタイズ: LLMからの応答をブラウザへ転送する前に、HTMLタグや実行可能なコードが含まれていないか、あるいはDOM汚染のリスクがないかを確認する。
—
4. 結び:耐量子暗号と次世代の監査観点
最後に、少し未来の話をしよう。現在、WebSocketはTLS 1.3によって暗号化されているが、将来的な「量子コンピュータによる解読(Shorのアルゴリズム等)」を考慮すれば、現在のRSAやECDSAを用いた鍵交換は脆弱と言わざるを得ない。
高セキュリティな金融や国家機密を扱うインフラでは、すでに耐量子暗号(PQC: Post-Quantum Cryptography)への移行が始まっている。WebSocketの実装においても、TLS 1.3の鍵交換にKyberのようなアルゴリズムを採用したプロキシ層を前段に置くなど、物理層に近いレイヤでの防衛強化が今後必須となる。
チーフホワイトハッカーとしての提言:
「WebSocketは便利だ。しかし、それは脆弱なパイプラインである」という前提を忘れてはならない。認証はハンドシェイクで終えるな。メッセージのバリデーションは、スキーマ定義だけでなく「意味論的な解析(Semantic Analysis)」まで踏み込め。
脆弱性は常に、設計の「境界線」に潜んでいる。あなたのシステムの境界線がどこにあるのか、今一度再確認することをお勧めする。
コメント