Skip to content

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 the nginx.conf.
  • log_format is an attack surface you can't see. A logging choice alone ($request_filename in 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, fresh Set-Cookie session 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_status if 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-Svc header, nghttpx/QUIC-capable LB in the trace) is the go/no-go.
  • proxy_request_buffering on is 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_client session-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 shared without explicit ssl_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 yaml in 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_pass SSRF 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.