Upgrade Header Smuggling
H2C Smuggling
HTTP/2 Over Cleartext (H2C)
H2C, or HTTP/2 over cleartext, can be negotiated by upgrading an HTTP/1.1 connection to the HTTP/2 binary protocol. Both HTTP/1.1 and HTTP/2 can use persistent connections; the security-relevant change is that, after the upgrade, subsequent HTTP/2 streams may pass through a proxy-created tunnel.[1][2]
The crux of the smuggling issue arises with the use of a reverse proxy. Ordinarily, the proxy processes and forwards each HTTP request and returns the backend response. When it accepts an Upgrade, however, it may switch to blindly forwarding bytes between the client and backend. A compliant H2C upgrade request includes these three headers:[1][2]
Upgrade: h2c
HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA
Connection: Upgrade, HTTP2-Settings
The vulnerability arises when, after upgrading a connection, the reverse proxy ceases to manage individual requests, assuming its job of routing is complete post-connection establishment. Exploiting H2C Smuggling allows for circumvention of reverse proxy rules applied during request processing, such as path-based routing, authentication, and WAF processing, assuming an H2C connection is successfully initiated.[1][2]
Vulnerable Proxies
The vulnerability depends on how the reverse proxy handles Upgrade and Connection. The cited research found the following proxies forwarded the relevant headers by default in the tested configurations:[1][2]
- HAProxy
- Traefik
- Nuster
Conversely, the following services did not forward both headers by default in the tested configurations, but an unsafe configuration can still pass them through:
- AWS ALB/CLB
- NGINX
- Apache
- Squid
- Varnish
- Kong
- Envoy
- Apache Traffic Server[1]
Exploitation
Not all proxies forward the headers required for a compliant H2C upgrade. The tested default configurations of AWS ALB/CLB, NGINX, and Apache Traffic Server blocked the compliant form. Also test the noncompliant Connection: Upgrade variant, which omits HTTP2-Settings from the Connection header, because some proxy/backend combinations process it differently.[1][2]
[!CAUTION] Irrespective of the specific path designated in the
proxy_passURL (e.g.,http://backend:9999/socket.io), the established connection defaults tohttp://backend:9999. This allows for interaction with any path within that internal endpoint, leveraging this technique. Consequently, the specification of a path in theproxy_passURL does not restrict access.
The tools h2csmuggler by BishopFox and h2csmuggler by assetnote facilitate attempts to circumvent proxy-imposed protections by establishing an H2C connection, thereby enabling access to resources shielded by the proxy.[2][1]
For additional information on this vulnerability, particularly concerning NGINX, refer to this detailed resource.
Websocket Smuggling
WebSocket smuggling creates a tunneled connection through a proxy whose frontend and backend disagree about whether a WebSocket upgrade succeeded. The tunnel can bypass path or authentication controls enforced only by the frontend.[3]
Scenario 1
In this scenario, a backend that offers a public WebSocket API alongside an inaccessible internal REST API is targeted by a malicious client seeking access to the internal REST API. The attack unfolds in several steps:
- The client initiates by sending an Upgrade request to the reverse proxy with an incorrect
Sec-WebSocket-Versionprotocol version in the header. The proxy, failing to validate theSec-WebSocket-Versionheader, believes the Upgrade request to be valid and forwards it to the backend. - The backend responds with a status code
426, indicating the incorrect protocol version in theSec-WebSocket-Versionheader. The reverse proxy, overlooking the backend’s response status, assumes readiness for WebSocket communication and relays the response to the client. - Consequently, the reverse proxy is misled into believing a WebSocket connection has been established between the client and backend, while in reality, the backend had rejected the Upgrade request. Despite this, the proxy maintains an open TCP or TLS connection between the client and backend, allowing the client unrestricted access to the private REST API through this connection.
The cited research reproduced this scenario with Varnish and Envoy 1.8.0; later Envoy versions changed the upgrade mechanism. Test other proxies rather than assuming that they share the same behavior.[3]

Scenario 2
This scenario involves a backend with both a public WebSocket API and a public REST API for health checking, along with an inaccessible internal REST API. The attack, more complex, involves the following steps:
- The client sends a POST request to trigger the health check API, including an additional HTTP header
Upgrade: websocket. NGINX, serving as the reverse proxy, interprets this as a standard Upgrade request based solely on theUpgradeheader, neglecting the request’s other aspects, and forwards it to the backend. - The backend executes the health-check API and requests an external resource controlled by the attacker, which returns an HTTP response with status code
101. When the backend forwards that response, NGINX incorrectly treats it as confirmation that the original client connection was upgraded.

Warning: This technique’s complexity increases as it requires the ability to interact with an endpoint capable of returning a status code 101.
Ultimately, NGINX is tricked into believing a WebSocket connection exists between the client and the backend. In reality, no such connection exists; the health check REST API was the target. Nevertheless, the reverse proxy maintains the connection open, enabling the client to access the private REST API through it.

This scenario additionally requires an SSRF-like endpoint whose upstream response is relayed to the proxy. Susceptibility depends on the proxy’s upgrade validation and the application’s response-forwarding behavior.[3]
Labs
Check the labs to test both scenarios in https://github.com/0ang3el/websocket-smuggle.git[3]