diff options
| author | srdusr <[email protected]> | 2026-06-30 14:52:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2026-06-30 14:52:00 +0200 |
| commit | 384573a2dc5e3b8e2a7bdfe2ce949f2c52ba2c52 (patch) | |
| tree | 13bfe97b1f5bcf90e3fad33d90b522b29f3e439d /README.md | |
| parent | 2ade8c807584bff0b60d6b6f278dbde29b13a5ff (diff) | |
| download | mitmux-384573a2dc5e3b8e2a7bdfe2ce949f2c52ba2c52.tar.gz mitmux-384573a2dc5e3b8e2a7bdfe2ce949f2c52ba2c52.zip | |
WebSocket interception
The last "known limitation": a ws://wss:// connection stops being
one-shot request/response the instant its 101 Switching Protocols
lands, and forward()'s normal write-response-then-record flow has no
way to represent that. Scoped to HTTP/1.1 client legs (HTTP/2 can't be
hijacked for raw post-response access the way HTTP/1.1 can, and
browsers open a dedicated HTTP/1.1 connection for WebSocket regardless
of the surrounding page's protocol, so this isn't a real-world gap).
internal/proxy/websocket.go decodes each RFC 6455 frame's opcode and
payload for capture while relaying the exact same raw bytes it read
unmodified - this is capture, not tampering, matching the rest of the
codebase's raw-bytes-as-source-of-truth stance. One row per frame, not
per reassembled message (fragmentation is rare in real-world
WebSocket traffic; not worth buffering an unbounded number of pending
fragments to handle it). forward() branches on a matching 101 into
handleWebSocketUpgrade, which hijacks the client connection, relays
the handshake response raw, records the upgrade request/response to
history normally, then relays frames bidirectionally into a new
ws_messages table - reachable from the TUI's detail view via `w`.
Found and fixed two real bugs by actually driving a WebSocket
connection through a running daemon, not by reading the code:
stripHopByHop was deleting Connection/Upgrade from every outgoing
request (correct for an ordinary request per RFC 7230, catastrophic
for one asking to upgrade - every WebSocket attempt silently became a
426); and the relay tore the whole connection down the instant either
side saw a close frame, before the peer's own close-frame reply could
be relayed back, producing an abrupt EOF instead of a clean close.
Verified live end to end on both paths a real client uses: ws://
(plain HTTP forward-proxying) against a Python websockets echo
server, and wss:// (CONNECT-tunneled, TLS-intercepted) against the
same server behind TLS - text, binary, and extended-length frames,
plus a full close handshake with both directions' close frames
present, confirmed via the actual bytes captured in ws_messages.
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 23 |
1 files changed, 21 insertions, 2 deletions
@@ -23,6 +23,9 @@ list of what's deliberately not implemented (and why), see independently on the client and upstream legs - a client that only speaks HTTP/1.1 and an origin that prefers HTTP/2 both work correctly in the same request. +- **WebSocket**: `ws://`/`wss://` connections are relayed byte-for-byte + unmodified with every frame captured for display, not just the + upgrade handshake - see WebSocket below. - **History**: every request/response captured to SQLite. Raw wire bytes are preserved byte-for-byte on HTTP/1.1 legs (what request smuggling and parser-differential analysis actually needs); HTTP/2 @@ -258,7 +261,22 @@ response (display-only - never touches the stored or resent bytes), `c` mark/compare (same as the history list), `r`/`i` jump straight to Repeater/Intruder seeded from this entry, `e` exports the entry (request and response, raw bytes, plain text - type a path and press -enter), `esc` back. +enter), `w` views captured WebSocket messages if this entry's +connection was upgraded (see WebSocket below), `esc` back. + +### WebSocket + +A `ws://` or `wss://` request that gets a matching `101 Switching +Protocols` back stops being one-shot request/response - mitmux relays +every frame byte-for-byte unmodified in both directions (this is +capture, not tampering) while decoding each one's payload for display. +Press `w` from an upgraded entry's detail view to see them: direction, +opcode (text/binary/close/ping/pong), size, and a preview; `enter` on +a row shows that frame's full decoded payload. One row per frame, not +per reassembled logical message - a message fragmented across several +frames (rare in real-world WebSocket traffic: JSON events, chat +messages, game state are almost always single-frame) shows up as +several rows rather than being stitched back together. ### Comparer @@ -571,7 +589,8 @@ reasoning behind each: 1000 requests per attack across all four modes - `mitmuxd -install-ca` prints per-OS trust-store install steps; it never runs them for you (see Quick start above for why) -- No WebSocket interception +- WebSocket messages are captured one row per frame, not reassembled + from fragments (rare in real-world traffic) - see WebSocket below - No active or passive vulnerability scanning, no plugin system - this is a manual-testing tool, not a scanner |