リアルタイムチャット・株価ダッシュボード・IoT機器の制御画面。これらに共通するWebSocket通信が、あなたのシステムに盲点を生み出している可能性があります。
WebSocketは「一度コネクションを確立すれば、サーバーとクライアントが自由にデータをやり取りできる」便利な技術ですが、従来のHTTPとは異なる通信モデルを持つため、標準的なCSRF対策やCookieのSameSite属性が期待通りに機能しないことがあります。
この記事では、WebSocketに特有のセキュリティリスクを攻撃者の視点で解説し、現場で使えるレベルの防御手順をお伝えします。実装例はあくまで防御設計の参考として示しており、悪用を助長するものではありません。

WebSocketとは?なぜ今セキュリティが重要か
WebSocket(RFC 6455)は、クライアントとサーバーがHTTPのハンドシェイクを経て双方向の全二重通信チャネルを確立するプロトコルです。HTTPがリクエスト・レスポンスの1対1のやり取りであるのに対し、WebSocketは一度接続すれば両者が随時データを送れる「常時接続」を実現します。
チャット・リアルタイム通知・ゲーム・金融取引画面など、インタラクティブなWebアプリケーションへの普及が急速に進んでいます。一方でOWASP APIセキュリティTop 10でもリアルタイムAPIの認証・認可の弱点が繰り返し指摘されており、WebSocketは攻撃者にとって魅力的なターゲットになっています。
接続が確立した後の通信はHTTPのステートレスな特性を持たないため、従来の防御機構が使えない場面が多く、設計段階からのセキュリティ考慮が不可欠です。
WebSocketが抱える主なセキュリティリスク(敵を知る)
WebSocket特有のリスクは主に4つに分類されます。
・CSWSH(クロスサイトWebSocket乗っ取り): 悪意のあるページが被害者のブラウザを使って正規サーバーとのWebSocket接続を勝手に確立し、認証済みセッションを悪用する攻撃
・認証・認可の欠落: WebSocket接続後のメッセージに対して権限チェックが行われず、低権限ユーザーが高権限の操作を実行できてしまう
・メッセージ検証不備(インジェクション): WebSocket経由で受け取ったデータをそのままDBクエリやシェルコマンドに渡すことで、SQLインジェクションやOSコマンドインジェクションが発生する
・暗号化の欠如(ws://平文接続): TLSなしの接続(ws://)を使うと通信内容が盗聴・改ざんされるリスクがある
これらはいずれも「WebSocketはHTTPとは別物」という認識不足から生まれる設計ミスが原因です。
CSWSH(クロスサイトWebSocket乗っ取り)の仕組みと被害例
CSWSH(Cross-Site WebSocket Hijacking)は、WebSocketのハンドシェイクがHTTPリクエストで行われる仕組みを悪用した攻撃です。
# CSWSH攻撃の流れ(防御目的での概念説明) # 1. 被害者が victim.example.com に認証済みでログイン中 # 2. 被害者が攻撃者のサイト attacker.example.com を閲覧する # 3. 攻撃者ページのJavaScriptが以下を実行 # const ws = new WebSocket("wss://victim.example.com/ws"); # 4. ブラウザは被害者のセッションCookieを自動付与してハンドシェイク # 5. サーバーはCookieだけを見て「認証済み」と判断し接続を許可 # 6. 攻撃者は被害者の権限でメッセージの読み書き・操作が可能になる
通常のCSRF対策(SameSite Cookie等)はWebSocketのHTTPアップグレードリクエストには必ずしも適用されないため、サーバー側で明示的にOriginを検証しないと防ぐことができません。
実際の被害事例としては、チャット内容の盗み見・メッセージの偽造・管理機能の不正操作などが報告されています。また、確立した接続を内部ネットワーク探索(SSRF的な踏み台)に悪用したケースも確認されています。
具体的な防御手順
1. Originヘッダーの検証(ホワイトリスト方式)
WebSocketハンドシェイクにはブラウザが自動的にOriginヘッダーを付与します。サーバー側でこの値をホワイトリストと照合することが、CSWSH対策の出発点です。
# Python(websocketsライブラリ)での Origin 検証例 import websockets import asyncio ALLOWED_ORIGINS = {"https://www.example.com", "https://app.example.com"} async def handler(websocket): origin = websocket.request_headers.get("Origin", "") if origin not in ALLOWED_ORIGINS: # 許可されていないオリジンは即切断 await websocket.close(1008, "Origin not allowed") return # 以降の通常処理 async for message in websocket: await process_message(websocket, message)
ただし、Originヘッダーはブラウザが自動付与するものであり、curlなどのコマンドラインツールは任意の値を送れます。Origin検証は「ブラウザベースのCSWSH」を防ぐ対策であり、それ単体で認証の代替にはなりません。後述のトークン認証と必ず組み合わせてください。
2. トークンベース認証の実装
WebSocket接続には、セッションCookieだけに依存せず、CSRFトークンやJWTを検証する仕組みを組み込むことを推奨します。
・最初のメッセージ認証方式(推奨): 接続直後の最初のメッセージでトークンを送信し、検証が通るまで他のメッセージを受け付けない
・クエリパラメータ方式: wss://api.example.com/ws?token=xxxx のようにURLにトークンを含める(URLはサーバーログに残るため、トークンの有効期限を短く設定し使い捨てにする)
# 最初のメッセージで認証する実装パターン(擬似コード) import asyncio, json async def handler(websocket): try: # タイムアウト付きで最初のメッセージを待つ raw = await asyncio.wait_for(websocket.recv(), timeout=5.0) msg = json.loads(raw) token = msg.get("token", "") except (asyncio.TimeoutError, json.JSONDecodeError): await websocket.close(1008, "Auth required") return user = verify_token(token) # トークン検証関数 if not user: await websocket.close(1008, "Unauthorized") return # 認証後は user 情報を使って通常の処理を行う async for message in websocket: await handle_message(websocket, message, user)
3. 入力検証とメッセージのサニタイズ
WebSocket経由で受信したデータも、HTTPリクエストと同じく外部入力として扱います。受け取るすべてのメッセージに対して型チェック・長さ制限・入力値検証を実施し、DBや外部コマンドに渡す前には適切にエスケープ処理を行います。
・JSONスキーマ検証: 受信メッセージをJSONスキーマで検証し、想定外のフィールドや型を拒否する
・レート制限: 短時間に大量のメッセージを送りつけるDoS攻撃を防ぐため、接続ごとのメッセージ送信レートを制限する
・メッセージサイズ上限: 極端に大きなメッセージによるメモリ消費攻撃を防ぐため、1メッセージあたりのサイズ上限を設定する(例: 64KBなど用途に応じた値)
・型安全な処理: SQLやシェルコマンドにメッセージ内容を渡す場合は、プリペアドステートメント・パラメータバインドを使用する
4. wss://(TLS)の強制とセキュリティヘッダー設定
WebSocket通信は必ずwss://(TLS暗号化)を使用してください。ws://(平文)は公衆Wi-Fiや共有ネットワーク上での盗聴・改ざんリスクがあります。
WebSocketを使うページ自体のHTTPレスポンスに適切なセキュリティヘッダーを設定することで、不正なオリジンからのWebSocket接続確立を抑制できます。Content-Security-Policy(CSP)のconnect-srcディレクティブでWebSocketの接続先ドメインを明示的に許可リストに入れることも有効です。
TLS設定のベストプラクティスについては、TLS 1.3移行完全ガイドも合わせてご参照ください。
中小企業でも今日からできること
WebSocketを直接実装していなくても、導入済みのSaaSや社内アプリが内部でWebSocketを使っているケースは増えています。まずは現状把握から始めましょう。
・使用箇所の棚卸し: Chromeの開発者ツール(ネットワークタブ → WS フィルター)で社内アプリのWebSocket使用状況を確認する
・ws://接続の廃止: 平文接続が残っているアプリがあれば、wss://への移行を開発チームに指示する
・開発ガイドラインへの追記: 社内の開発規約にWebSocket固有のセキュリティ要件(Origin検証・トークン認証・入力検証)を追記する
・WAFの設定確認: 導入済みのWAFがWebSocketのアップグレードリクエストやメッセージを監視・フィルタリングしているか確認する(APIセキュリティの設定もあわせて見直す)
・サードパーティサービスの確認: チャットボットやサポートツール等で外部WebSocketサービスを組み込んでいる場合は、そのサービスのセキュリティ設定とデータ取り扱い方針を確認する
よくある誤解と注意点
【誤解1】HTTPSサイトだからWebSocketも安全
ページがHTTPSであっても、WebSocket接続自体がws://(平文)の場合は盗聴リスクが残ります。HTTPSはページ配信の暗号化であり、WebSocket通信の暗号化とは別物です。必ずwss://を使用してください。
【誤解2】CookieがあるからWebSocketの認証は不要
CSWSH攻撃はセッションCookieが正当に付与されることを前提に成立します。CookieのSameSite属性をStrictに設定することは有効な緩和策の一つですが、それだけに頼るのは危険です。Origin検証とトークン認証を組み合わせた多層防御を構築してください。
【注意】セッション失効後も接続が生き続けるリスク
WebSocketはロングリブド接続(長時間維持される接続)であるため、ユーザーがログアウトしたあとも接続が残り続ける可能性があります。ユーザーのログアウト・セッション失効時にはサーバー側から能動的にWebSocket接続を切断する設計を組み込んでください。

本記事のまとめ
| リスク | 対策 | 優先度 |
|---|---|---|
| CSWSH(クロスサイト乗っ取り) | Originヘッダーのホワイトリスト検証 | 高(即対応) |
| 認証・認可の欠落 | トークンベース認証の実装 | 高(即対応) |
| メッセージインジェクション | 入力検証・スキーマ検証・エスケープ | 高(即対応) |
| 平文通信(ws://) | wss://(TLS)への完全移行 | 高(即対応) |
| DoS・メッセージ爆弾 | レート制限・メッセージサイズ上限 | 中 |
| セッション失効後の接続残存 | サーバー側からの能動的切断実装 | 中 |
WebSocketは「接続が確立した後が本番」です。HTTPと異なり、一度コネクションが開いてしまえばリクエストのたびに認証チェックが自動で走るわけではありません。接続時の厳格な認証と、メッセージごとの権限確認を設計に組み込むことが、安全なリアルタイムアプリケーションの基本です。
Linuxサーバー上でWebSocketサーバーを運用している場合は、姉妹サイトLinuxMaster.JPのサーバーセキュリティ関連記事もあわせてご活用ください。
PR
体系的に学ぶ 安全なWebアプリケーションの作り方 第2版(徳丸浩/SBクリエイティブ)
WebSocketを含むWebアプリ全般の脆弱性と対策を体系的に学べる国内最定番の一冊。CSWSH・インジェクション・認証設計まで実装レベルで解説されており、開発者・インフラエンジニア双方に役立ちます。
