Your C2 Is a WebSocket Upgrade and the Proxy Logged It as Chat
HTTP beacons are so 2018. the implant sends Connection: Upgrade, Sec-WebSocket-Protocol: chat.v1, Origin set to something wholesome, and then rides a long-lived TLS session exchanging JSON frames that look like typing indicators. your proxy wrote status 101 and went back to sleep. your SIEM does not have a use case for “socket that never ends.”
127.0.0.1 on a disposable lab. do not point WebSocket C2 at production chat infrastructure you do not own. authorized channel testing and detection engineering only.the trick in one paragraph
WebSockets (RFC 6455) start as HTTP and become a bidirectional frame pipe after a 101 upgrade. to a TLS-inspecting proxy that does not parse frames, that is “a browser opened a socket.” to a C2 author, that is interactive tasking without the awkward rhythm of periodic GETs. subprotocols, Origin headers, and browser-like JA3 make it rude to fingerprint from metadata alone.

step 1: stand up a toy C2
websockets.serve(handler, '127.0.0.1', 8443, subprotocols=['chat.v1'])
real operators put this behind nginx or Cloudflare with a boring path like /socket or /ws/v1/notifications. the lab stays local so you can see both ends without involving a CDN ToS team.

step 2: implant side
connect with a browser-ish Origin and the agreed subprotocol. send a hello. receive JSON ops. respond. encrypt inside the frames if you are not already trusting TLS alone (you should not). jitter by staying connected and whispering occasionally instead of reconnecting every sixty seconds like a guilty HTTP beacon.

step 3: what logs actually capture
most enterprise proxies record the upgrade and then… nothing useful. bytes in/out maybe. not opcodes. not that every binary frame is 512 bytes on a five-minute metronome. if you terminate TLS, you can do better. if you do not, you are trusting SNI and vibes.

why defenders care
- 101 is not benign by default: it is a protocol switch you probably do not parse.
- beacon math changes: duration and frame cadence matter more than request count.
- domain fronting cosplay: WS over a “known” chat or notifications host blends into allowlists.
- interactive C2: operators get shells that feel local without opening raw TCP 443 to an IP from 2009.
controls that are not theater
- allowlist WebSocket destinations per app. default deny for WS egress from servers.
- terminate TLS on a proxy that logs subprotocol, Origin, frame counts, and duration.
- alert: WS sessions > N minutes from hosts that are not browsers; binary frame size entropy; unknown subprotocols.
- JA4 / process correlation: python/curl doing WS from a finance VM is a ticket.
- for red team: scope the domains, document the upgrade path, tear down when the test ends.
sloth take
you taught the SOC to hate strange POSTs to /gate.php. attackers upgraded the connection and kept talking. status 101 is not a participation trophy. parse the frames or accept that “chat” is how shells commute now.
