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.
5 steps from start to impact.
Obtain Ingress create/update rights in any namespace
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.- 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
- 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
Craft malicious Ingress with quote-terminated path
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.- Ingress admission webhook (ValidatingAdmissionPolicy) does not block quote characters in path fields
- No pre-admission Kyverno/OPA policy on rewrite-target
- Kubernetes 1.30+ ValidatingAdmissionPolicy can block the payload if operators wrote a rule
- Kyverno signed policies in production clusters often already restrict annotation values
Controller reloads nginx.conf with attacker directives
/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.- Controller successfully validates and loads generated config (it does — the injection is syntactically valid nginx)
- None — this is the intended reconciliation path
Read cluster-wide Secrets via controller's ServiceAccount token
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.- Controller ServiceAccount has default cluster-wide Secret read (this is the shipped default in Helm chart and manifests)
- Rare hardened installs scope the controller ClusterRole to specific namespaces — this narrows blast radius but does not stop RCE inside the pod
secrets list requests from the ingress-nginx SA at unusual volume; cloud CSPM flags mass Secret enumerationPivot to cluster-admin or cloud tenant
- Any Secret in the cluster contains upstream credentials (near-universal)
- Workload Identity / IRSA / GKE Workload Identity Federation without static Secrets reduces cloud pivot value — but does NOT block cluster-admin token theft
The supporting signals.
| In-the-wild status | No 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 PoC | Full walkthrough and payload at github.com/SnailSploit/CVE-2026-3288 — includes content_by_lua_block Secret-exfil variant. |
| EPSS | 0.06669 (~6.7% probability of exploitation in 30d) — elevated for a K8s add-on CVE requiring RBAC. |
| KEV status | Not listed on CISA KEV as of 2026-07-02. |
| CVSS vector | AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H — PR:L accurately reflects the required Ingress-write RBAC. Scope is U but blast radius exceeds a single component in practice. |
| Affected versions | ingress-nginx < 1.13.8, < 1.14.4, < 1.15.0. Same class of bug as CVE-2026-24512 (incomplete fix). |
| Fixed versions | 1.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 data | Shodan/Censys count ~340k internet-reachable ingress-nginx instances (server header fingerprint); Sysdig telemetry shows ingress-nginx present in ~44% of surveyed K8s clusters. |
| Disclosure | Reported by Nir Ohfeld & Sagi Tzadik (Wiz Research); coordinated disclosure via Kubernetes SRC. Public advisory 2026-03-09 on oss-security. |
| Reporter | Wiz Research, credited jointly with the Kubernetes Security Response Committee (kubernetes-security@). |
noisgate verdict.
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.
Why this verdict
- Role multiplier — ingress-nginx is infrastructure, not app tier: the default Helm install grants the controller SA
secretsread 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:Lgating, 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:Lis 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.
What to do — in priority order.
- Deploy an admission policy blocking
"in Ingress path fields and restrictingrewrite-target— Ship a Kyverno or ValidatingAdmissionPolicy (K8s 1.30+) that rejects any Ingress whosespec.rules[].http.paths[].pathcontains a"character or whosenginx.ingress.kubernetes.io/rewrite-targetannotation 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. - 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.
- Audit and prune Ingress-write RBAC — Run
kubectl auth can-i --list --assweeps to enumerate every principal withcreate/updateoningresses. 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. - 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
secretslistAPI calls from the ingress-nginx SA above baseline. Alerts here catch the post-injection stage even if admission control is bypassed. - 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.
- 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 isrewrite-target, not a snippet annotation.
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.
#!/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
If you remember one thing.
" 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
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.