Files
Kordant/piolium/findings/p8-008-websocket-jwt-query-param/report.md
2026-05-29 09:03:47 -04:00

49 lines
2.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Phase: 8
Sequence: 008
Slug: websocket-jwt-query-param
Verdict: VALID
Rationale: WebSocket JWT passed in query parameter is visible in server/proxy/access logs; captured JWTs can be replayed to hijack WebSocket connections
Severity-Original: medium
Severity: medium
PoC-Status: pending
Pre-FP-Flag: none
Debate: piolium/attack-surface/balanced-chamber-summary.md
## Summary
The WebSocket server in `web/src/server/websocket.ts` authenticates connections by extracting a JWT from the `?token=[REDACTED:secret]] query parameter. This means the JWT token is visible in server access logs, proxy/load balancer logs (nginx, CloudFront, Vercel edge logs), browser network history, and any log aggregation system. An attacker with access to these logs can capture JWTs and connect to the WebSocket server as any user.
## Location
- `web/src/server/websocket.ts` lines 3943 (token extraction)
- `web/src/server/websocket.ts` lines 5667 (authentication)
## Attacker Control
An attacker with access to server/proxy logs can capture JWTs from WebSocket connection URLs. The captured JWT can be replayed to establish WebSocket connections as the victim user.
## Trust Boundary Crossed
Authentication boundary. JWT tokens are exposed through log layers, allowing attackers to authenticate as any user by replaying captured tokens.
## Impact
JWT token leakage through server logs enables WebSocket connection hijacking for any user whose token appears in logs. The attacker gains read-only access to real-time alerts (darkwatch exposures, voiceprint alerts, spam notifications).
## Evidence
```typescript
function getTokenFromRequest(req: IncomingMessage): string | null {
const url = new URL(req.url ?? "/", "http://localhost");
return url.searchParams.get("token"); // JWT in query string
}
```
## Reproduction Steps
1. Legitimate user connects WebSocket with `ws://host:3001/?token=[REDACTED:secret]]
2. Server logs the full URL including the JWT token
3. Attacker gains access to server logs (via log aggregation compromise, shared hosting, etc.)
4. Attacker replays the JWT to establish a WebSocket connection as the victim
5. Attacker receives real-time alerts for the victim's account
## Defense Search Results
- JWT verification (`verifyJWT()`) validates signature and expiry
- No `Origin` header validation on WebSocket upgrade (see p8-009)
- No `Sec-WebSocket-Protocol` header validation
- No message size limit
- Heartbeat timeout (30s interval + 10s pong timeout) prevents slow-loris DoS