← Back to Feed CACHED · 2026-07-02 15:20:19 · CACHE_KEY CVE-2026-3288
CVE-2026-3288 · CWE-20 · Disclosed 2026-03-09

ingress-nginx rewrite-target nginx configuration injection

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

A quotation mark in a YAML path field turns your ingress controller into a cluster-wide keyring dump

CVE-2026-3288 is a configuration injection in the nginx.ingress.kubernetes.io/rewrite-target handling path of ingress-nginx. The controller interpolates the Ingress spec.rules.http.paths.path value directly into a double-quoted nginx regex inside buildProxyPass() without escaping. A " character in the path closes the quoted string prematurely and the remainder is written into nginx.conf as raw directives — enough to load an arbitrary Lua block, exfiltrate the controller's ServiceAccount token, or shell out. Affected versions: ingress-nginx < 1.13.8, < 1.14.4, < 1.15.0. Fixed in 1.13.8 / 1.14.4 / 1.15.0, released 2026-03-04.

This is the *incomplete fix* to CVE-2026-24512. When maintainers introduced sanitizeQuotedRegex() in February 2026 to protect buildLocation(), they missed the identical sink in buildProxyPass(). The vendor's HIGH / 8.8 rating is accurate and arguably conservative — because the default ingress-nginx install ships a ClusterRole that can read Secrets *cluster-wide*, a successful injection turns any tenant with Ingress-write RBAC into a de facto cluster admin. In a shared/multi-tenant cluster the practical outcome is fleet-scale credential theft.

"Any developer with Ingress create rights can steal every Secret in the cluster. Vendor got this one right — patch it."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Obtain Ingress create/update rights in any namespace

Attacker needs a Kubernetes identity with create/update on networking.k8s.io/ingresses in at least one namespace. In practice this is any app-team service account, developer kubeconfig, or CI runner in a shared cluster. In managed platforms (Rancher, OpenShift, GKE-shared) this is granted by default to project members.
Conditions required:
  • Valid kubeconfig or SA token with Ingress-write RBAC in ≥1 namespace
  • Target cluster runs vulnerable ingress-nginx < 1.13.8/1.14.4/1.15.0
Where this breaks in practice:
  • Fully locked-down clusters that restrict Ingress creation to a platform team via OPA/Kyverno reduce reachable population
  • Some managed offerings (EKS with AWS Load Balancer Controller default) don't use ingress-nginx
Detection/coverage: kube-audit logs show Ingress create events; Falco/Sysdig ships a rule for CVE-2026-3288 payloads in Ingress specs
STEP 02

Craft malicious Ingress with quote-terminated path

Attacker submits an Ingress with spec.rules.http.paths.path containing " followed by nginx directives, and the rewrite-target annotation set. Public PoC lives at github.com/SnailSploit/CVE-2026-3288 — the payload injects a content_by_lua_block that reads /var/run/secrets/kubernetes.io/serviceaccount/token.
Conditions required:
  • Ingress admission webhook (ValidatingAdmissionPolicy) does not block quote characters in path fields
  • No pre-admission Kyverno/OPA policy on rewrite-target
Where this breaks in practice:
  • Kubernetes 1.30+ ValidatingAdmissionPolicy can block the payload if operators wrote a rule
  • Kyverno signed policies in production clusters often already restrict annotation values
Detection/coverage: Sysdig, Aqua, Prisma, and Wiz shipped detections for anomalous quote characters in Ingress path fields within 72h of disclosure
STEP 03

Controller reloads nginx.conf with attacker directives

The ingress-nginx controller reconciles the new Ingress, templates it into /etc/nginx/nginx.conf, and hot-reloads. Injected Lua now runs *inside* the controller pod on every matching request — and the controller pod runs as the ingress-nginx ServiceAccount.
Conditions required:
  • Controller successfully validates and loads generated config (it does — the injection is syntactically valid nginx)
Where this breaks in practice:
  • None — this is the intended reconciliation path
Detection/coverage: Container runtime EDR (Sysdig Secure, Falco, Aqua Enforcer) can flag unexpected process spawn from nginx worker or unexpected outbound HTTP from controller pod
STEP 04

Read cluster-wide Secrets via controller's ServiceAccount token

The controller's default ClusterRole includes secrets get/list cluster-wide (required to read TLS material for arbitrary Ingress TLS blocks). Attacker Lua code calls the kube API with the mounted token and exfiltrates every Secret — including ServiceAccount tokens for cluster-admin bindings, cloud IAM credentials in workload Secrets, and CI/CD deploy keys.
Conditions required:
  • Controller ServiceAccount has default cluster-wide Secret read (this is the shipped default in Helm chart and manifests)
Where this breaks in practice:
  • Rare hardened installs scope the controller ClusterRole to specific namespaces — this narrows blast radius but does not stop RCE inside the pod
Detection/coverage: kube-audit secrets list requests from the ingress-nginx SA at unusual volume; cloud CSPM flags mass Secret enumeration
STEP 05

Pivot to cluster-admin or cloud tenant

With stolen Secrets containing cluster-admin bindings, cloud IAM keys, or SSO refresh tokens, attacker authenticates outside the cluster: kubectl as cluster-admin, AWS/GCP/Azure API as workload identity, GitHub/GitLab as CI deploy token. In multi-tenant clusters this is a full tenant-boundary bypass.
Conditions required:
  • Any Secret in the cluster contains upstream credentials (near-universal)
Where this breaks in practice:
  • Workload Identity / IRSA / GKE Workload Identity Federation without static Secrets reduces cloud pivot value — but does NOT block cluster-admin token theft
Detection/coverage: CloudTrail / GCP Audit for unusual API calls from harvested IAM keys; kube-audit for cross-namespace token use
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo confirmed mass exploitation as of 2026-07-02; no KEV listing. Wiz and Sysdig both report active scanning against /healthz on internet-exposed ingress-nginx from Censys-indexed IPs post-disclosure.
Public PoCFull walkthrough and payload at github.com/SnailSploit/CVE-2026-3288 — includes content_by_lua_block Secret-exfil variant.
EPSS0.06669 (~6.7% probability of exploitation in 30d) — elevated for a K8s add-on CVE requiring RBAC.
KEV statusNot listed on CISA KEV as of 2026-07-02.
CVSS vectorAV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HPR:L accurately reflects the required Ingress-write RBAC. Scope is U but blast radius exceeds a single component in practice.
Affected versionsingress-nginx < 1.13.8, < 1.14.4, < 1.15.0. Same class of bug as CVE-2026-24512 (incomplete fix).
Fixed versions1.13.8, 1.14.4, 1.15.0 (released 2026-03-04). Helm chart bumps: ingress-nginx-4.13.8, 4.14.4, 4.15.0.
Exposure dataShodan/Censys count ~340k internet-reachable ingress-nginx instances (server header fingerprint); Sysdig telemetry shows ingress-nginx present in ~44% of surveyed K8s clusters.
DisclosureReported by Nir Ohfeld & Sagi Tzadik (Wiz Research); coordinated disclosure via Kubernetes SRC. Public advisory 2026-03-09 on oss-security.
ReporterWiz Research, credited jointly with the Kubernetes Security Response Committee (kubernetes-security@).
04 · The Call

noisgate verdict.

Final Verdict
= UNCHANGED to HIGH (8.8/10)

This stays HIGH because the affected component is a canonical high-value role (cluster ingress controller with default cluster-wide Secret read), and the single decisive factor is the role multiplier — ingress-nginx compromise = cluster-scale credential theft. It does not clear the CRITICAL bar because PR:L requires an authenticated Kubernetes identity with Ingress-write RBAC, no unauthenticated internet path, and no active in-the-wild campaign or KEV listing.

HIGH Technical impact and affected version ranges
HIGH Public PoC availability and reproducibility
MEDIUM Real-world exploitation prevalence (early signals, no confirmed campaigns)

Why this verdict

  • Role multiplier — ingress-nginx is infrastructure, not app tier: the default Helm install grants the controller SA secrets read cluster-wide. RCE in that pod = every Secret in the cluster, which means every ServiceAccount token, every TLS key, every cloud workload credential. Chain reliably ends in cluster-admin.
  • Friction floor — PR:L is a low bar in multi-tenant clusters: most enterprise K8s clusters grant Ingress-write to project/namespace admins by default. In a shared cluster this is effectively tenant-to-cluster-admin escalation with a one-line YAML payload.
  • Installed base is enormous: ingress-nginx is the most-deployed K8s ingress controller (~44% of clusters per Sysdig, ~340k internet-reachable instances per Censys). Even with PR:L gating, the reachable population is large.
  • No downgrade for exposure: even air-gapped/private clusters are in scope — the attacker vector is a K8s API call, not an HTTP request to the ingress data plane. Firewalling the LB does not help.
  • Not CRITICAL: no unauthenticated path, no KEV, no confirmed campaign, EPSS still under 10%. PR:L is meaningful enough to keep this out of the CRITICAL bucket for now.

Why not higher?

No unauthenticated remote path — the attacker must already have an authenticated Kubernetes identity with Ingress-write RBAC. No KEV listing and no confirmed mass-exploitation campaign as of 2026-07-02. EPSS remains under 10%. If a campaign emerges (Wiz/Sysdig telemetry begins showing post-exploitation Secret enumeration in the wild), this becomes CRITICAL immediately.

Why not lower?

MEDIUM would ignore the deployment-role floor: ingress-nginx *is* a canonical high-value component, cluster-wide Secret read is the shipped default, and a public PoC exists. Downgrading to MEDIUM would require evidence that the vast majority of installs restrict the controller ClusterRole and enforce admission policies on Ingress annotations — neither is the industry norm.

05 · Compensating Control

What to do — in priority order.

  1. Deploy an admission policy blocking " in Ingress path fields and restricting rewrite-target — Ship a Kyverno or ValidatingAdmissionPolicy (K8s 1.30+) that rejects any Ingress whose spec.rules[].http.paths[].path contains a " character or whose nginx.ingress.kubernetes.io/rewrite-target annotation contains suspicious characters. Deploy across all clusters within the noisgate HIGH mitigation window — ≤ 30 days — as a bridge until every cluster is on 1.13.8/1.14.4/1.15.0.
  2. Scope the ingress-nginx ClusterRole to namespaces you actually run Ingress in — The default install grants cluster-wide Secret read. Rewrite the ClusterRole to a set of RoleBindings scoped to the namespaces hosting Ingress resources. This does not prevent RCE in the controller pod but bounds the blast radius from *cluster-wide* to *scoped namespaces*. Roll out in the same 30-day mitigation window.
  3. Audit and prune Ingress-write RBAC — Run kubectl auth can-i --list --as sweeps to enumerate every principal with create/update on ingresses. Remove access from CI service accounts, sandbox tenants, and low-trust developers. Do this before you deploy the patch — you'll want the pre/post delta for audit.
  4. Enable runtime detection on the ingress-nginx controller pod — Add Falco/Sysdig/Aqua rules for: unexpected outbound HTTP from nginx worker, unexpected process spawn from nginx pid, and secrets list API calls from the ingress-nginx SA above baseline. Alerts here catch the post-injection stage even if admission control is bypassed.
  5. Upgrade to 1.13.8 / 1.14.4 / 1.15.0 — Actual remediation. Bump the Helm chart (ingress-nginx-4.13.8+) or manifest to the fixed version. Because ingress-nginx is a data-plane component, use a rolling replacement and validate custom snippets still template correctly. Complete within the noisgate HIGH remediation window — ≤ 180 days, but prioritize toward the front of that window.
What doesn't work
  • WAF / ModSecurity in front of ingress-nginx — the injection happens at Ingress *admission* to the K8s API, not on data-plane traffic. The WAF never sees the payload.
  • Network policies restricting pod-to-pod traffic — the controller's Secret access uses its mounted SA token against the API server; NetworkPolicy on the controller pod doesn't stop it (the API server is in kube-system).
  • Making the LoadBalancer internal-only — same as above. The vulnerability is triggered via kube-apiserver, not the ingress data plane. An air-gapped cluster is still fully exposed to a tenant with kubectl access.
  • allow-snippet-annotations: false — this hardened CVE-2025-1974 (IngressNightmare) but does NOT block CVE-2026-3288, because the injection sink is rewrite-target, not a snippet annotation.
06 · Verification

Crowdsourced verification payload.

Run this on an auditor workstation with a kubeconfig that has read access to the target cluster (kubectl get pods -A should work). Invoke as ./check-cve-2026-3288.sh [context-name]. Reads the deployed ingress-nginx image tag and compares against fixed versions. Requires kubectl and jq.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# noisgate verifier: CVE-2026-3288 (ingress-nginx rewrite-target injection)
# Exit: 0 PATCHED, 1 VULNERABLE, 2 UNKNOWN
set -u
CTX="${1:-}"
KUBECTL="kubectl"
[ -n "$CTX" ] && KUBECTL="kubectl --context=$CTX"

command -v jq >/dev/null || { echo "UNKNOWN: jq missing"; exit 2; }
command -v kubectl >/dev/null || { echo "UNKNOWN: kubectl missing"; exit 2; }

# Find any Deployment/DaemonSet running an ingress-nginx controller image
PODS=$($KUBECTL get pods -A -o json 2>/dev/null | \
  jq -r '.items[] | select(.spec.containers[]?.image | test("ingress-nginx/controller")) | \
    [.metadata.namespace, .metadata.name, (.spec.containers[] | select(.image|test("ingress-nginx/controller")) | .image)] | @tsv')

if [ -z "$PODS" ]; then
  echo "UNKNOWN: no ingress-nginx controller pods found in this cluster"
  exit 2
fi

vuln=0
unknown=0
while IFS=$'\t' read -r ns name image; do
  tag=$(echo "$image" | sed -E 's/.*:v?([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
  if ! echo "$tag" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
    echo "UNKNOWN: $ns/$name image=$image (cannot parse tag)"
    unknown=1
    continue
  fi
  IFS=. read -r maj min pat <<< "$tag"
  # Fixed: 1.13.8, 1.14.4, 1.15.0 (and any 1.15+ / 1.x >1.15)
  fixed=1
  if [ "$maj" -eq 1 ]; then
    if [ "$min" -lt 13 ]; then fixed=0
    elif [ "$min" -eq 13 ] && [ "$pat" -lt 8 ]; then fixed=0
    elif [ "$min" -eq 14 ] && [ "$pat" -lt 4 ]; then fixed=0
    fi
  elif [ "$maj" -lt 1 ]; then
    fixed=0
  fi
  if [ "$fixed" -eq 0 ]; then
    echo "VULNERABLE: $ns/$name running $tag"
    vuln=1
  else
    echo "PATCHED: $ns/$name running $tag"
  fi
done <<< "$PODS"

if [ "$vuln" -eq 1 ]; then exit 1; fi
if [ "$unknown" -eq 1 ]; then exit 2; fi
exit 0
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: enumerate every cluster with ingress-nginx and pin the running controller version. Per the noisgate mitigation SLA for HIGH, ship a Kyverno/ValidatingAdmissionPolicy that rejects " in Ingress path fields and constrains rewrite-target annotation values across all clusters within 30 days, and audit Ingress-write RBAC in the same window — this is your bridge control. Per the noisgate remediation SLA for HIGH, complete the upgrade to ingress-nginx 1.13.8 / 1.14.4 / 1.15.0 across the entire fleet within 180 days, but front-load it — production and multi-tenant clusters should be first, and any cluster where the controller ClusterRole is still the default cluster-wide-Secret-read should be treated as your highest risk regardless of internet exposure. If Wiz/Sysdig telemetry shifts to confirmed in-the-wild exploitation, this jumps to CRITICAL and the mitigation window collapses to hours.

Sources

  1. Kubernetes issue #137560 — CVE-2026-3288
  2. Kubernetes Security Advisory (discuss.kubernetes.io)
  3. oss-security disclosure post
  4. Sysdig — detecting CVE-2026-3288 & CVE-2026-24512
  5. SnailSploit PoC and walkthrough
  6. SnailSploit technical writeup
  7. TheHackerWire — RCE and Secret Disclosure analysis
  8. NVD entry
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.