auditing-http2-and-grpc-multiplexing-trust · git:20260829.b60f9b2 · 2026-08-29 · sha256 36005fb32e6dc68d
auditing-http2-and-grpc-multiplexing-trust git:20260829.b60f9b2A
Immutable. This exact content is served forever at /api/v1/blob/36005fb32e6dc68d.
--- name: auditing-http2-and-grpc-multiplexing-trust description: >- Audit HTTP/2 and gRPC edges for framing and multiplexing trust that breaks when a stream is translated or reused: an h2c or HTTP/2-to-HTTP/1.1 downgrade that reintroduces request smuggling, pseudo-header and header handling that lets a stream forge its path or authority, multiplexed streams on one connection whose authentication or rate limit is applied per connection rather than per stream, and a gRPC gateway that trusts metadata or a method name a caller controls. Covers HTTP/2 front ends, gRPC services, and gateways that translate between protocols. Use when an edge terminates or downgrades HTTP/2 or multiplexes gRPC calls and per-stream trust is assumed. The crafted stream or metadata is the source, the back-end request or method it reaches is the sink, and the downgrade or per-connection trust that admits it is the bug. license: MIT --- # Auditing HTTP/2 and gRPC multiplexing trust: when a stream is not what the connection claims HTTP/2 and gRPC move the unit of a request from a connection to a stream: one connection carries many interleaved streams, each with its own headers, pseudo-headers, and path. Trust decisions that were written for one-request-per-connection quietly break here. A downgrade seam, HTTP/2 translated to HTTP/1.1 at the origin, or cleartext h2c smuggled past a front end, reintroduces the framing ambiguity of request smuggling in a protocol that was supposed to have solved it. Pseudo-headers like the authority and path are attacker-set per stream, so a stream can claim a path or host the front end never authorized. And per-connection controls, authentication established once, a rate limit counted per connection, are undercounted when many streams share the connection. A gRPC gateway adds method names and metadata a caller controls. The audit asks whether trust is enforced per stream and whether translation preserves the framing. You audit this by testing each stream and each downgrade seam rather than trusting the connection. ## When to use - An edge terminates, downgrades, or translates HTTP/2, including h2c or HTTP/2-to-HTTP/1.1 to an origin. - gRPC calls are multiplexed on shared connections and authentication or rate limits may be per connection. - A gRPC gateway trusts caller-supplied metadata, method names, or pseudo-headers to route or authorize. ## Scope check Test HTTP/2 and gRPC edges only on systems you own or are authorized to assess, on non-production endpoints. A downgrade smuggling proof can poison other streams or requests, so use a dedicated test origin and keep payloads benign. If you can't name the authorization, stop. ## The loop 1. **Establish the intended per-stream trust first.** Name what each stream should be allowed to do and how authentication, path authorization, and rate limits are meant to apply: per stream, on verified attributes. This is the false-positive killer: an edge that authenticates and authorizes every stream independently on attributes it verifies, and downgrades without reintroducing framing ambiguity, is correct. Name the intended per-stream trust, then test against it. 2. **Test the downgrade and translation seam.** Where HTTP/2 is translated to HTTP/1.1 or h2c is accepted, test whether the translation reintroduces request smuggling: a stream whose translated framing the origin parses differently, or an h2c upgrade smuggled past a front end that only inspects HTTP/1.1. The translation is a parser seam, so the smuggling discipline applies here in HTTP/2 clothing. 3. **Check pseudo-header and authority handling.** For each stream, confirm the edge validates the authority and path pseudo-headers and does not let a stream forge a host or path the connection was not authorized for. A stream that sets an authority pointing at an internal service, or a path that bypasses front-end routing rules, is using per-stream attributes to escape connection-level trust. 4. **Check per-stream versus per-connection enforcement.** Determine whether authentication, authorization, and rate limits are applied per stream or once per connection. Authentication established at connection setup and then trusted for every stream lets one authenticated connection carry streams that should be checked individually; a rate limit counted per connection is bypassed by multiplexing many streams. Confirm the controls count and check streams, not connections. 5. **Check gRPC gateway metadata and method trust.** For a gRPC gateway, confirm it does not authorize or route on caller-controlled metadata or the method name without verification: metadata a client sets is not an identity, and a method name is a request, not a permission. Trace whether attacker-supplied metadata reaches an authorization decision or a back-end call. 6. **Confirm and record.** Confirm on a dedicated test origin by downgrading a stream into a smuggled request, forging an authority or path that reaches an unauthorized back end, exhausting a per-connection rate limit with multiplexed streams, or authorizing on forged metadata, without touching real traffic. Kill the lead if the downgrade preserves framing, pseudo-headers are validated, trust is enforced per stream, and gateway metadata is not trusted. Record the crafted stream or metadata, the back-end request or method sink, and the downgrade or per-connection trust that admitted it. ## Where multiplexing trust leaks - **A downgrade reintroduces smuggling.** HTTP/2-to-HTTP/1.1 translation or accepted h2c re-creates the framing disagreement across the translation seam. - **Pseudo-headers forge path and authority.** A stream sets its own authority and path, so weak validation lets it claim a host or route the connection was not authorized for. - **Per-connection auth trusts every stream.** Authentication established once and applied to all streams on the connection skips per-stream checks that should run individually. - **Per-connection rate limits undercount streams.** A limit counted per connection is bypassed by multiplexing many streams on one connection. - **Gateway metadata and method names are caller input.** gRPC metadata a client sets is not an identity, and a method name is a request, not an authorization. ## Worked example (a confirm and a kill) > **Confirm.** A front end accepts an h2c cleartext upgrade it does not fully inspect and forwards to an origin > that re-parses the stream as HTTP/1.1. On a dedicated test origin, a stream smuggled through the upgrade is > parsed by the origin as a separate request reaching an internal path the front end would have blocked. > **Confirmed** HTTP/2 downgrade smuggling to an unauthorized back end, `high`, remediation = reject or fully > inspect h2c upgrades at the edge, ensure HTTP/2-to-HTTP/1.1 translation preserves framing unambiguously, and > apply path authorization to the translated request at the origin. > > **Kill.** The edge rejects unexpected h2c upgrades, translates HTTP/2 to the origin with unambiguous framing > the origin parses identically, validates the authority and path pseudo-headers on every stream against the > allowed routes, authenticates and rate-limits per stream rather than per connection, and the gRPC gateway > authorizes on verified identity rather than caller metadata. A crafted stream reaches only what it is > authorized for. **Killed**, `kill_reason` = "downgrade preserves framing, pseudo-headers validated per stream, > auth and limits enforced per stream, and gateway metadata not trusted; no stream escapes its authorized > scope." ## Rationalizations to reject - *"HTTP/2 fixed request smuggling."* → Only end to end; a downgrade or h2c seam to an HTTP/1.1 origin brings the framing ambiguity right back. - *"The connection is authenticated."* → Authentication per connection is not per stream; confirm each stream is checked, not just the connection it rides. - *"We rate-limit the connection."* → Multiplexing many streams on one connection bypasses a per-connection limit; count streams. - *"The gateway passes our metadata."* → Client-set metadata is caller input, not identity; confirm authorization rests on verified attributes, not what the caller sent. - *"The path is set by the client, that is normal."* → The authority and path pseudo-headers are attacker-set per stream; validate them or a stream forges its route. ## Executing this in practice You need the downgrade and translation points, how framing is preserved across each, the pseudo-header validation, whether authentication, authorization, and rate limits are per stream or per connection, and the gRPC gateway's metadata and method trust. For each stream and seam, test whether trust holds independently. Reading the edge and gateway configuration shows the intended per-stream trust; a downgrade smuggle or a per-connection bypass on a test origin shows whether it holds. ## Related - `hunting-http-request-smuggling-and-desync` - the HTTP/1.1 foundation; the downgrade seam here is that same desync reached through HTTP/2. - `auditing-grpc-service-authorization` - the gRPC authorization companion; this skill covers the multiplexing and gateway-metadata edge, that one the per-method authorization. - `auditing-service-mesh-mtls-and-authz-trust` - meshes carry gRPC over HTTP/2; per-stream trust and mesh authorization are the same question at different layers. - `mapping-attack-surface` - use it to find HTTP/2 edges, h2c listeners, and gRPC gateways before testing their per-stream trust. - [FINDING-SCHEMA.md](../../FINDING-SCHEMA.md) - source = the crafted stream or metadata, sink = the back-end request or method it reaches, evidence = the downgrade or per-connection trust that admitted it.