NGINX coordinated wave (8 GHSAs): SSI/AIO UAF, map/alias heap overflows, HTTP/3 request injection, TLS-session client-cert forgery, and Ingress/Gateway-Fabric control-plane legs¶
Wave in one line: the most-deployed edge proxy on the internet shipped two critical memory-corruption legs that are configuration-shaped (SSI+AIO, map regex capture ordering, alias+$request_filename), an HTTP/3 request-injection leg, a client-certificate bypass built from forged TLS sessions, an authentication-subrequest header relay in the Kubernetes Ingress Controller, and two tenant-to-platform legs in Gateway Fabric — every one of them testable by configuration fingerprint before you ever touch a memory-safety primitive.
Signal: GitHub Security Advisories published a coordinated eight-advisory wave against F5 NGINX Plus / NGINX Open Source / NGINX Kubernetes Ingress Controller (NIC) / NGINX Gateway Fabric at 2026-10-14T15:32:30Z. All GHSA package entries carry empty version fields — the fix bands live in the vendor advisories (F5 K-series for the core product, GitHub releases for NIC/Gateway Fabric), so build fingerprinting comes first: nginx -V output, the Server: banner, and for the Kubernetes controllers the CRD schema/labels on the cluster. None of the eight CVEs were in CISA KEV at catalog 2026.10.13 at scan time. This is a data-plane-and-control-plane mixed release with F5 explicitly labeling each leg; do not assume the memory-corruption legs are the only reachable ones.
Advisory table¶
| GHSA | CVE | CVSS | Boundary |
|---|---|---|---|
| GHSA-ff5w-vvxc-fm9x | CVE-2026-97205 | critical | ngx_http_core_module: with async AIO enabled in certain configurations, serving an SSI page containing <!--#include virtual="..." --> triggers a use-after-free in the worker. Memory corruption / worker crash; code execution where ASLR is disabled or bypassed. |
| GHSA-v72q-v4vf-2mx7 | CVE-2026-93479 | critical | map directive with regex matching, where root or log_format references the map's regex capture variables before the map output variable (or a non-cacheable variable sits in a string expression): unauthenticated crafted requests → heap buffer overflow in the worker. |
| GHSA-cvv4-j4mx-w8q5 | CVE-2026-15176 | critical | Location using alias together with rewrite and a set/return referencing $request_filename (or $request_filename in log_format): crafted requests → heap buffer overflow in the worker. |
| GHSA-g6r5-95cx-mpp9 | CVE-2026-95101 | medium | ngx_http_v3_module: HTTP/3 with proxy_request_buffering on — an unauthenticated attacker can inject data into the proxied request. Request-smuggling-shaped injection on the newest transport. |
| GHSA-qhwc-5g9q-3669 | CVE-2026-97629 | high | ssl_session_cache shared: without explicit ssl_session_ticket_key (auto-rotated ticket keys): a remote unauthenticated attacker can forge client SSL sessions, non-deterministically, to bypass client-certificate verification (mTLS bypass). |
| GHSA-5m5m-935w-8wj6 | CVE-2026-94197 | high | NGINX Kubernetes Ingress Controller with ExternalAuth/auth_request: client-supplied X-Forwarded-Prefix, X-Forwarded-URI, X-Original-URI, X-Original-Method/X-Forwarded-Method headers relayed unsanitized into the internal auth subrequest → spoof the attributes the external auth provider evaluates → unauthenticated access to protected paths. |
| GHSA-8rxj-rxx8-xj47 | CVE-2026-91184 | high | NGINX Gateway Fabric renders spec.basic.realm / spec.jwt.realm of a namespaced AuthenticationFilter verbatim into the shared data-plane nginx.conf (auth_basic "<realm>";). A namespace-scoped user closes the quoted directive and the enclosing location {} block → arbitrary NGINX directive injection into the shared Gateway config: read co-located tenants' mounted secrets, SSRF via proxy_pass, read the pod ServiceAccount token, hijack other tenants' routes. |
| GHSA-h8jf-9855-344m | CVE-2026-96615 | high | NGINX Gateway Fabric with Gateway CRDs + NGINX Agent gRPC: a namespaced authenticated service account can impersonate the agent's gRPC connection → read TLS private keys of Gateway deployments in other namespaces. |
Durable operator axes¶
1. Config-shaped memory corruption: fingerprint the config shape, not the version¶
All three overflow/UAF legs (97205, 93479, 15176) are gated by configuration features, not just versions: SSI + aio/aio_threads, a map with regex captures consumed in a particular variable order, an alias+rewrite+$request_filename combination, or $request_filename inside log_format. Operator value:
- The trigger surface is inferable from behavior.
alias-served static locations,map-driven routing, and SSI-processed pages all have externally observable shapes (404 bodies, per-host variable routing, SSI include timing/error text). Build the config-shape recon from the outside before assuming you cannot assess these legs without thenginx.conf. log_formatis an attack surface you can't see. A logging choice alone ($request_filenamein the format string) arms 15176 for every request, on every route, with no visible response change. Standing rule for the whole parser family: passive config state (logs, internal variables) arms active-path bugs — you cannot rule these legs out from response diffs; rule them IN with crafted inputs and watch for worker-restart signatures (connection reset on keep-alive, freshSet-Cookiesession state, brief 502 behind the LB, upstream keepalive pool churn).- Worker restart is your oracle. NGINX master/worker design means a successful memory-safety trigger is usually a crash-and-respawn, visible as a per-request availability blip while the master stays up. On authorized engagements, a benign crafted-input battery against candidate locations with restart-rate monitoring is the whole validation loop; code-exec claims need ASLR-disabled or an ASLR bypass and should stay lab-only.
- Build fingerprint first: F5's GHSA entries ship empty version ranges — resolve bands from the vendor advisory for the exact build (
nginx -V,X-Build-style headers on NGINX Plus,/nginx_statusif exposed). Patch skew between the core product and the Kubernetes controllers (NIC/Gateway Fabric release independently) is the usual open-leg window.
2. HTTP/3 request injection: re-test smuggling classes on the new transport¶
g6r5/95101 is an unauthenticated data-injection into the proxied request under HTTP/3 + proxy_request_buffering on. This is the classic front-end/back-end request-boundary differential class, moved to QUIC where most desync test suites do not reach. Rules:
- Transport parity is a test obligation. Every request-framing/smuggling probe you own for HTTP/1.1 (CL.TE, TE.CL, chunked folding, whitespace variants) and HTTP/2 (stream/pseudo-header desyncs) deserves a re-run over HTTP/3 against any target advertising
alt-svc: h3. The injection primitive landed specifically in the buffering module's H3 path — the fingerprint that a proxy fronts H3 (Alt-Svcheader,nghttpx/QUIC-capable LB in the trace) is the go/no-go. proxy_request_buffering onis the default — assume the armed state unless the config says otherwise.
3. Forged TLS sessions defeat client certs: session-state is an authorization input¶
qhwc/97629 is the sleeper: mTLS-protected endpoints where the server relies on ssl_session_cache shared: with auto-rotated session-ticket keys can have client sessions forged so a new connection inherits a session whose peer-cert state is already "verified." Durable axes:
- Resumption state bypasses the handshake gate. Session IDs/tickets are credentials that assert handshake outcome. Where the server rotates ticket keys itself, the assertion is forgeable. On authorized tests against mTLS-gated admin/management interfaces: attempt
openssl s_clientsession-resumption flows against an endpoint you never completed a certified handshake on, and record accept-vs-reject of the application request under a forged session. Accept = the mTLS gate is theater on that vhost. - Config-shaped, like everything else in this wave: exposure requires
ssl_session_cache sharedwithout explicitssl_session_ticket_key. You cannot see this from outside; for repo/config-access engagements, grep it. For black-box, it is one of the checks to request in a white-box mTLS review.
4. Auth-subrequest header relay: the forged-original-URI family hits auth_request¶
5m5m/94197 (NIC ExternalAuth) is a direct sibling of this wiki's standing forged original-URI header rule (Anubis X-Original-URI challenge bypass on the Sept 15 header-route page, proxy-addr hop-0 forgery on the same page): any middleware that forwards a client header naming the request into a decision component lets the client choose which policy bucket it is evaluated in. New here: the decision component is a subrequest, and the relayed set includes the method (X-Forwarded-Method) — so an auth provider that gates by path AND method sees a different verb than the one actually proxied (auth-allow GET /reports, proxied POST /reports). Sweep battery on any auth_request/ExternalAuth front: replay the same protected request with each of X-Forwarded-Prefix, X-Forwarded-URI, X-Original-URI, X-Forwarded-Method, X-Original-Method, X-Original-URL, X-Rewrite-URL set to an allowlisted path/safe method; positive = your request lands on the protected route with a 2xx the provider never approved. Evidence = provider access-log line showing the spoofed URI vs the actual upstream route hit.
5. CRD-string-into-shared-config: tenant-controlled text rebuilt into platform directives¶
8rxj/91184 (Gateway Fabric realm → auth_basic "<realm>";) is the config-template injection class — same genealogy as ingress-nginx annotation config injection (CVE-2026-1580): a controller serializes untrusted strings into a config-language document without escaping, and the quote/brace grammar is the exploit. Operator rules:
- Every CRD/annotation/label field a controller renders into proxy config is an injection candidate. Enumerate the CRD schemas (
kubectl get crd -o yamlin authorized clusters, or the controller's docs) and list every free-text field that lands in a directive argument:realm, headers, timeouts formatted into directives, custom snippets. The exploit primitive is the config grammar's own metacharacters:"to close the argument,;to end the directive,}/{to restructure blocks. - Shared-tenant data-plane = cross-tenant blast radius. The injected directives execute with the Gateway's identity: mounted secrets of co-located tenants,
proxy_passSSRF from the data-plane pod's network position, ServiceAccount token disclosure, route hijack of other tenants on the same Gateway. Namespaced permission (a low bar) → platform-read is the ladder. - NIC/Gateway Fabric/Gateway all share config-generation DNA. If one product in the family had an annotation/field injection leg (ingress-nginx 2026-1580, this one), fuzz siblings with the same field shapes first. h8jf/96615 completes the pair: even the control-plane gRPC channel (NGINX Agent) is an impersonable identity boundary — a namespaced SA reaching the agent socket/port can pull other namespaces' TLS private keys. Ingress controllers are a two-surface target: generated-config grammar + internal RPC surface.
Validation boundaries¶
- Memory-safety legs: authorized lab builds only for any crash/code-exec pursuit; production assessment is limited to configuration-shape fingerprinting and, with written scope, crafted-input worker-restart observation. Never pursue RCE claims outside a lab.
- HTTP/3 injection and auth-header relay: single-connection canaries against owned backends; record raw bytes; effects limited to route/cache/auth-boundary proofs on your own accounts. No cross-user desync.
- mTLS forgery: only against client-cert endpoints in scope, ideally lab; application-layer effect only.
- CRD injection: namespaced test workload with synthetic secrets in an owned cluster; never read real co-tenant material or ServiceAccount tokens into report evidence.
- h8jf gRPC impersonation: owned cluster, synthetic Gateway, redact all key material; presence-of-read is the finding.
Report framing¶
One page per affected product surface you actually tested. For the core product, lead with the configuration-shape finding (SSI+AIO / map-capture-order / alias+$request_filename present on this build) and attach the CVE; the memory-corruption claim is the vendor's, your contribution is reachability + restart evidence. For NIC, lead with the provider-log-vs-route decision table. For Gateway Fabric, lead with the cross-tenant read primitive name (co-tenant secret / SA token / private key) and the injected directive verbatim from a synthetic test Gateway.