WebSocket smuggling abuses the HTTP upgrade process rather than classic Content-Length or Transfer-Encoding parser mismatches. The goal is to trick a front-end proxy into believing a connection has been upgraded to WebSocket, while the back-end still processes traffic as plain HTTP. Once that happens, the proxy may tunnel later bytes without inspection, letting you bypass frontend restrictions and reach protected backend paths.
HTTP is request-response driven, so the client normally has to ask before the server can send anything back. That is awkward for real-time features such as notifications, chat, or live updates.
WebSockets solve this by creating a long-lived, full-duplex channel between browser and server.
WebSockets are designed to begin as HTTP. The client sends an HTTP request containing headers such as Upgrade: WebSocket, Connection: Upgrade, Sec-WebSocket-Key, and Sec-WebSocket-Version. If the server accepts the request, it replies with 101 Switching Protocols.
After that point, the connection is no longer treated as ordinary HTTP traffic.
Many proxies do not terminate WebSockets themselves. Instead, they forward the upgrade request to the backend and, if they think the upgrade succeeded, they switch into tunnel mode.
In tunnel mode, later traffic is relayed between client and server with little or no HTTP-aware inspection.
The attack is to create disagreement about whether the upgrade really happened.
A practical way to trigger this is by sending an unsupported Sec-WebSocket-Version. Modern WebSocket implementations usually expect 13. If the version is invalid, the backend may reject the upgrade with 426 Upgrade Required.
If the proxy fails to verify that response and assumes the upgrade succeeded anyway, it may still start tunneling.
GET /socket HTTP/1.1
Host: target
Sec-WebSocket-Version: 777
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Key: nf6dB8Pb/BLinZ7UexUXHg==
GET /flag HTTP/1.1
Host: target
The first request tries and fails to upgrade. The second request is then forwarded through the tunnel if the proxy trusts the upgrade state more than the backend response.
This usually lets you tunnel your own requests through the frontend rather than poison other users' backend connections.
The common use case is bypassing proxy-enforced restrictions. If the frontend blocks direct access to /flag, /admin, or an internal-only route, tunneling can still make the backend process that request.
That makes this a frontend ACL bypass problem more than a shared desync poisoning issue.
Some proxies are so permissive that they do not even need the target path to be WebSocket-enabled. If the request simply looks enough like a WebSocket upgrade, the proxy may still switch into tunnel mode.
In those cases, even a request to / can be enough to trigger the behavior.
More careful proxies, such as properly configured Nginx, verify that the backend actually returned 101 Switching Protocols before tunneling traffic. That blocks the invalid-version trick because a 426 response clearly means no upgrade happened.
If the application contains SSRF and can be made to fetch a URL you control, you may be able to feed the backend a fake 101 Switching Protocols response. That gives the proxy the success response it wants without creating a real WebSocket session.
At that point, the proxy thinks the connection is upgraded, while the backend still processes subsequent bytes as HTTP.
101 Switching Protocols from that attacker-controlled server,import sys
from http.server import HTTPServer, BaseHTTPRequestHandler
class Redirect(BaseHTTPRequestHandler):
def do_GET(self):
self.protocol_version = "HTTP/1.1"
self.send_response(101)
self.end_headers()
HTTPServer(("", int(sys.argv[1])), Redirect).serve_forever()
A simple server like this is enough to fake the backend response if the SSRF can reach you.
Content-Length updates in Burp when sending handcrafted requests.nc can be more reliable.101 responses will block the simplest version.101 Switching Protocols.101 response when direct invalid-upgrade tricks fail.