---
key: "case-study-ingress-nginx"
title: "Case study: ingress-nginx CVE-2025-1974 and the exposed admission webhook (2025)"
url: "https://softwaresecurity.com/domains/runtime/containers/case-study-ingress-nginx/"
api: "https://softwaresecurity.com/api/v1/articles/case-study-ingress-nginx.json"
domain: "runtime"
topic: "Runtime, cloud \u0026 APIs"
type: "Case study"
identifiers: ["CWE-653","CWE-306","CWE-94","A02:2025","A05:2025"]
tags: ["Injection","Secrets management","Kubernetes and containers","Vulnerability management"]
stages: ["build","operate","respond"]
roles: ["developer","platform-sre"]
license: "CC-BY-4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
code_license: "MIT"
---

# Case study: ingress-nginx CVE-2025-1974 and the exposed admission webhook (2025)

How an admission webhook open to the whole Pod network made ingress-nginx's configuration-injection bugs a 9.8 cluster-takeover risk in 2025, and what its 2026 retirement means.

Canonical URL: https://softwaresecurity.com/domains/runtime/containers/case-study-ingress-nginx/

## In short

On 24 March 2025 the Kubernetes project disclosed five ingress-nginx vulnerabilities, led by CVE-2025-1974 (CVSS 9.8): its validating admission webhook accepted requests from anything on the Pod network without authentication, so anyone there could use configuration-injection bugs to run code in the controller. By default the controller can read every Secret in the cluster, so that often meant cluster takeover. Fixes shipped in v1.12.1 and v1.11.5; the interim mitigation was to turn the admission webhook off and back on after upgrading. In October 2026 there is a further problem: ingress-nginx was retired in March 2026 and gets no more security fixes, so the lasting fix is migration.

- An admission webhook is a network service. If anything on the Pod network can call it, it is part of your attack surface, not just the API server's.
- Four of the five CVEs were configuration injection through Ingress annotations or webhook input; CVE-2025-1974 removed the need for permission to create an Ingress.
- The controller's default access to all Secrets cluster-wide turned code execution in one pod into a cluster-level compromise.
- Fixed in v1.12.1 and v1.11.5; affected were v1.12.0, v1.11.0 to v1.11.4 and everything before v1.11.0.
- The Kubernetes issue reported no known indicators of compromise, and CVE-2025-1974 is not in CISA's KEV catalog; the name IngressNightmare and the exposure figures come from Wiz.
- ingress-nginx has been retired since March 2026, and its repository is archived; plan a move to Gateway API or another maintained controller.

## Incident at a glance

- Date: 2025-03-24 to 2026-03
- Category: Misconfiguration
- Organizations: Kubernetes project (ingress-nginx maintainers and SRC); Wiz (reporter)
- CVE: [CVE-2025-1974](https://nvd.nist.gov/vuln/detail/cve-2025-1974), [CVE-2025-24514](https://nvd.nist.gov/vuln/detail/cve-2025-24514), [CVE-2025-1097](https://nvd.nist.gov/vuln/detail/cve-2025-1097), [CVE-2025-1098](https://nvd.nist.gov/vuln/detail/cve-2025-1098), [CVE-2025-24513](https://nvd.nist.gov/vuln/detail/cve-2025-24513)
- Impact: An admission webhook open to the whole Pod network, plus default read access to every Secret, turned configuration-injection bugs in ingress-nginx into a cluster-takeover risk (CVSS 9.8). No exploitation was known at disclosure.
- How it was found: Reported to the Kubernetes Security Response Committee by researchers from Wiz and fixed in coordinated releases.

### Timeline

1. 2025-03-23: The Kubernetes SRC opens public issues for the five CVEs.
2. 2025-03-24: ingress-nginx v1.12.1 and v1.11.5 ship with fixes; the Kubernetes blog publishes mitigation guidance.
3. 2025-11-11: Kubernetes announces the retirement of ingress-nginx, with maintenance until March 2026.
4. 2026-03-19: The last controller release, v1.15.1, is published.
5. 2026-03: Maintenance ends and the repository is archived; no further security fixes.

## Recommended defaults

| Setting | Value | Note | Source |
| --- | --- | --- | --- |
| CVE-2025-1974 | CVSS 3.1 9.8, AV:N/AC:L/PR:N | Unauthenticated code execution in the controller from the Pod network. | kubernetes/kubernetes issue 131009 |
| Related CVEs | CVE-2025-24514, CVE-2025-1097, CVE-2025-1098 (8.8 each); CVE-2025-24513 (4.8) | Injection through the `auth-url`, `auth-tls-match-cn` and mirror annotations; a path traversal in the admission controller. | Kubernetes issues 131005 to 131008 |
| Fixed versions | v1.12.1 and v1.11.5 (released 24 March 2025) | Affected: before v1.11.0, v1.11.0 to v1.11.4, and v1.12.0. | Kubernetes blog; GitHub releases |
| Interim mitigation | Disable the validating admission webhook | Helm: `controller.admissionWebhooks.enabled=false`; turn it back on after upgrading. | Kubernetes blog |
| Default Secret access | All Secrets cluster-wide | Why code execution in the controller often meant cluster takeover. | Kubernetes blog |
| Project status | Retired: best-effort maintenance ended March 2026 | No further releases or security fixes; repository archived; last controller release v1.15.1 on 19 March 2026. | Kubernetes blog, 11 November 2025; GitHub |

## Verify it

1. Run `kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx` in every cluster, and check the image tags of anything that matches, including copies installed under other labels.
2. For every admission webhook in the cluster (`kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations`), find the pods behind it and confirm a NetworkPolicy lets only the control plane reach the webhook port.
3. Check what Secrets each controller's service account can read (`kubectl auth can-i list secrets --all-namespaces --as=system:serviceaccount:<ns>:<sa>`), and scope it down where the controller allows.
4. If you run ingress-nginx anywhere, record a migration plan and date: it has had no security fixes since March 2026.
5. Check that annotation validation is on wherever a controller turns annotations into configuration.

## What you will learn

- Explain how an unauthenticated admission webhook turned configuration-injection bugs into a 9.8 vulnerability.
- Find ingress-nginx and other webhook-bearing controllers in a cluster and judge their exposure.
- Restrict admission webhooks to the control plane and scope controller access to Secrets.
- Plan for a retired component, separating the project's statements from third-party research.

Audience: Platform and SRE teams who run Kubernetes clusters, and engineers who write controllers or operators with admission webhooks

Before you start:

- Know what an Ingress, an ingress controller and an admission webhook are.
- Know how Kubernetes RBAC grants a service account access to Secrets.
- For the code: Python 3 and `kubectl` access to a cluster for the inventory script.

Time: About 28 minutes: 10 to read and 18 for the exercise

## An admission webhook anyone in the cluster could call

ingress-nginx was the Kubernetes project's own ingress controller: it turned Ingress objects into nginx configuration and routed outside traffic to applications. The project's security blog said it was 'deployed in over 40% of Kubernetes clusters'. On 24 March 2025 the maintainers released v1.12.1 and v1.11.5 with fixes for five vulnerabilities, and the Kubernetes Security Response Committee called the situation 'very serious'.

Four of the bugs were about how the controller handled pieces of nginx configuration: 'a specially-crafted Ingress object can cause nginx to misbehave in various ways, including revealing the values of Secrets that are accessible to ingress-nginx.' Normally, exploiting them would need permission to create an Ingress, 'a fairly privileged action'. The fifth, CVE-2025-1974, rated 9.8, removed that requirement: it 'allows anything on the Pod network to exploit configuration injection vulnerabilities via the Validating Admission Controller feature of ingress-nginx.' In the project's words, 'anything on the Pod network has a good chance of taking over your Kubernetes cluster, with no credentials or administrative access required.'

The vulnerabilities were reported by researchers at Wiz, who named them IngressNightmare. The Kubernetes issue for CVE-2025-1974 stated: 'There are no known indicators of compromise that prove this vulnerability has been exploited', and the CVE is not in CISA's KEV catalog.

## Timeline



| When | Event | Source |
| --- | --- | --- |
| 2025-03-23 | The Kubernetes SRC opens issues 131005 to 131009 for CVE-2025-24513, CVE-2025-24514, CVE-2025-1097, CVE-2025-1098 and CVE-2025-1974. | kubernetes/kubernetes issues |
| 2025-03-24 | ingress-nginx v1.12.1 and v1.11.5 are released (just after midnight UTC on 25 March), and the Kubernetes blog explains the risk and the mitigation. | Kubernetes blog; GitHub releases |
| 2025-03-24 | Wiz publishes its research under the name IngressNightmare, with exposure estimates. | Wiz (third-party research) |
| 2025-11-11 | Kubernetes SIG Network and the Security Response Committee announce that ingress-nginx will be retired, with best-effort maintenance until March 2026. | Kubernetes blog |
| 2026-03-19 | The last controller release, v1.15.1, is published. | GitHub releases |
| March 2026 | Maintenance ends; the repository is archived. No further releases, bug fixes or security updates. | Kubernetes blog; GitHub |

## Why the webhook made injection bugs critical

A validating admission webhook is an HTTPS service that the Kubernetes API server calls to approve or reject objects before it stores them. ingress-nginx ran one inside the controller pod, on port 8443 in the Helm chart's defaults, to warn users about broken Ingress configurations. The issue for CVE-2025-1974 states the flaw plainly: 'an unauthenticated attacker with access to the pod network can achieve arbitrary code execution in the context of the ingress-nginx controller.' The webhook did not check that its caller was the API server.

Wiz, which found the bugs, describes the rest: when the admission controller processes a request it renders a temporary nginx configuration from the Ingress object and tests it with `nginx -t`, so injected directives were evaluated during validation. That detail is from Wiz's write-up, not the Kubernetes advisories, which describe only 'configuration injection'. The three injection CVEs named the fields involved: the `auth-url`, `auth-tls-match-cn`, and `mirror-target` and `mirror-host` annotations. For CVE-2025-24514, the issue notes that the `enable-annotation-validation` argument, on by default from v1.12.0, prevents it.

This is the injection pattern the injection guide (/domains/appsec/secure-coding/injection-parser-boundaries/) describes, in infrastructure code: user-controlled strings copied into a configuration language that has powerful directives. Treat annotations and snippets as untrusted input to a parser, validate them against a strict grammar, and never pass them through to a configuration file unescaped.

## Why one pod meant the whole cluster

Code execution in a controller pod is bad; what made this cluster-wide was the controller's permissions. Both the blog and the issues repeat the point: 'in the default installation, the controller can access all Secrets cluster-wide.' An ingress controller needs TLS certificates, so its service account was given read access to Secrets in every namespace, and that includes service account tokens, database passwords and cloud credentials.

The Pod network was the other half. The blog warns that 'In many common scenarios, the Pod network is accessible to all workloads in your cloud VPC, or even anyone connected to your corporate network!' Wiz reported that about 43% of cloud environments it analyzed were vulnerable and that it found over 6,500 clusters exposing vulnerable admission controllers to the public internet; those are Wiz's figures, not the project's.

In the OWASP Kubernetes Top 10 (2025 edition) this sits across K06 Overly Exposed Kubernetes Components, K02 Overly Permissive Authorization Configurations and K05 Missing Network Segmentation Controls. The workload security guide (/domains/runtime/containers/kubernetes-workload-security/) covers service account scoping, and the Secrets guide (/domains/identity-crypto/secrets/secrets-in-kubernetes-and-runtime/) what a stolen Secret exposes.

## Fix, interim mitigation, and the retirement

The fix was to upgrade to v1.12.1, v1.11.5 or later. For teams that could not upgrade at once, the blog's interim mitigation was to turn off the Validating Admission Controller: with Helm, reinstall with `controller.admissionWebhooks.enabled=false`; for manual installs, delete the ValidatingWebhookConfiguration called `ingress-nginx-admission` and remove `--validating-webhook` from the controller's arguments. And then: 'remember to turn it back on after you upgrade', because the webhook catches broken Ingress objects before they take effect.

The story did not end with the patch. On 11 November 2025 Kubernetes SIG Network and the Security Response Committee announced the retirement of ingress-nginx: 'Best-effort maintenance will continue until March 2026. Afterward, there will be no further releases, no bugfixes, and no updates to resolve any security vulnerabilities that may be discovered.' Existing deployments keep working and artifacts stay available, and the GitHub repository is now archived. They recommend Gateway API, or another maintained ingress controller, and urge users to begin migrating immediately. A controller that receives no security fixes is a standing exposure; record it in your dependency inventory like any end-of-life component (/domains/appsec/vulnerability-response/dependency-vulnerability-response/).

## Find ingress-nginx controllers and their versions

The blog's check finds pods by the standard label. The script below reads the full pod list instead, so it also catches controllers installed under other labels, and reports each controller image with a verdict. It ran with Python 3.14 on a sample pod list: v1.12.0 and v1.9.6 were flagged as vulnerable, v1.11.5 as fixed but retired. Image tags can lie about what is inside; when in doubt, check the digest against the project's release notes.

```python
"""List ingress-nginx controllers and flag versions without the March 2025 fixes.

Usage: kubectl get pods -A -o json | python3 find_ingress_nginx.py
Fixed: v1.11.5 and v1.12.1 (CVE-2025-1974 and four related CVEs). The project
was retired in March 2026, so every version is now unsupported: plan a migration.
"""
import json
import re
import sys

FIXED = {(1, 11): (1, 11, 5), (1, 12): (1, 12, 1)}


def verdict(version):
    if version is None:
        return "unknown version: check the image"
    fixed = FIXED.get(version[:2])
    if version < (1, 11, 0) or (fixed and version < fixed):
        return "VULNERABLE to CVE-2025-1974: upgrade or disable the admission webhook"
    return "has the March 2025 fixes; project retired, plan a migration"


for pod in json.load(sys.stdin)["items"]:
    for container in pod["spec"]["containers"]:
        image = container["image"]
        if "ingress-nginx/controller" not in image:
            continue
        match = re.search(r":v(\d+)\.(\d+)\.(\d+)", image)
        version = tuple(map(int, match.groups())) if match else None
        meta = pod["metadata"]
        print(f"{meta['namespace']}/{meta['name']}: {image.split('@')[0]}: {verdict(version)}")

```

## Let only the control plane reach admission webhooks

A webhook should accept connections only from the API server. The NetworkPolicy below selects the controller pods by the Helm chart's labels, keeps ports 80 and 443 open for the traffic the controller serves, and allows the webhook port, 8443 by default, only from an IP range. Which range depends on the cluster: on self-managed clusters it is the control-plane nodes; on managed services the control plane is usually outside the cluster network, so check your provider's documentation for the source addresses of webhook calls. The policy parses as valid YAML but was not applied to a live cluster for this page; test it with a positive and a negative probe as the network policy guide (/domains/runtime/containers/container-network-admission/) describes. It needs a network plugin that enforces NetworkPolicy.

```yaml
# Allow client traffic to the controller, and the admission webhook port only
# from the API server. Replace the CIDR with the address range your control
# plane uses to call webhooks; on managed clusters, check your provider's docs.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/component: controller
  policyTypes: [Ingress]
  ingress:
    - ports: # the traffic the controller exists to serve
        - {port: 80, protocol: TCP}
        - {port: 443, protocol: TCP}
    - from:
        - ipBlock: {cidr: 10.0.0.0/28} # placeholder: your control plane's source range
      ports:
        - {port: 8443, protocol: TCP} # validating admission webhook (chart default)

```

## Which controls would have stopped or limited it?



| Control | Who | SSDF practice or standard | Guide |
| --- | --- | --- | --- |
| Authenticate or network-restrict admission webhooks so only the API server can call them | Controller authors and operators | SSDF PW.9.1; OWASP Kubernetes Top 10 2025 K06 | /domains/runtime/containers/container-network-admission/ |
| Validate annotations against a strict grammar; never copy them into configuration unescaped | Controller authors | SSDF PW.5.1, PW.8.2 | /domains/appsec/secure-coding/injection-parser-boundaries/ |
| Least-privilege RBAC: no cluster-wide Secret access by default | Controller authors and operators | SSDF PW.9.1; K02 | /domains/runtime/containers/kubernetes-workload-security/ |
| Default-deny NetworkPolicy, so the Pod network is not one flat trust zone | Operators | K05 | /domains/runtime/containers/container-network-admission/ |
| Inventory cluster add-ons with versions, and patch them on the same clock as applications | Operators | SSDF RV.1.1, RV.2.2 | /domains/appsec/vulnerability-response/dependency-vulnerability-response/ |
| Replace end-of-life components before they need an emergency fix | Operators | SSDF PW.4.4 | /domains/runtime/containers/managed-kubernetes-hardening/ |
| Root-cause the class: every place that renders user input into configuration | Controller authors | SSDF RV.3.3 | /domains/appsec/secure-coding/injection-parser-boundaries/ |

## Lessons that still apply



- Every admission webhook is a network service inside your cluster. Restrict who can call it.
- Configuration languages are code. User input that becomes nginx, Envoy or any other configuration needs the same care as SQL.
- Scope controller permissions: read access to all Secrets turns any controller bug into a cluster compromise.
- A flat Pod network makes every pod an attacker's starting point. Default-deny between namespaces.
- Track the lifecycle of add-ons, not just their versions: ingress-nginx is now retired and will not be fixed again.

## What the public evidence does not establish

The Kubernetes advisories do not describe the exploitation technique beyond 'configuration injection' through the admission controller; the `nginx -t` mechanism comes from Wiz's write-up. The figures of about 43% of cloud environments and more than 6,500 exposed clusters are Wiz's estimates from its own data, and the over-40% deployment share is the Kubernetes blog's statement. No exploitation was known when the issues were published, and CVE-2025-1974 is not in CISA's KEV catalog as of its 8 October 2026 version; that is not proof it was never exploited. What happens to vulnerabilities found in ingress-nginx after March 2026 is set only by the retirement announcement: they will not be fixed by the project.

## Guided practice

It is October 2026. You run four Kubernetes clusters. Decide each one's exposure and the next step. Treat the facts as true.

### Supplied material

```text
C1: ingress-nginx v1.12.0; admission webhook enabled; no NetworkPolicies in any namespace; controller service account can list Secrets cluster-wide.
C2: ingress-nginx v1.11.5; admission webhook enabled; NetworkPolicy allows port 8443 only from the control-plane range; RBAC unchanged from the default.
C3: ingress-nginx v1.12.1 installed under a custom name, so the standard label selector finds nothing; a security review marked the cluster 'no ingress-nginx'.
C4: Gateway API with a maintained implementation; no ingress-nginx; a third-party operator in the cluster runs its own validating webhook on port 9443, reachable from all pods.
```

### 1. Fix C1

What is C1's exposure and what do you do first?

Worked answer: Critical: a vulnerable version, an open webhook, a flat Pod network and cluster-wide Secret access. Disable the admission webhook now, upgrade, rotate Secrets the controller could read if compromise cannot be ruled out, add a webhook NetworkPolicy, and plan migration off ingress-nginx.

### 2. Judge C2

C2 has the fix and a NetworkPolicy. Is it done?

Worked answer: It is patched for the March 2025 CVEs, but ingress-nginx has been unsupported since March 2026, so future flaws will not be fixed. Keep the policy, reduce the controller's Secret access, and schedule the migration.

### 3. Correct C3

What went wrong in C3's review?

Worked answer: The check relied on a label. Search pod images, not labels, as the script on this page does; then treat C3 like C2.

### 4. Look past ingress-nginx in C4

Is C4 clear?

Worked answer: Not of the pattern: the operator's webhook is reachable from every pod. Check whether it authenticates callers, restrict it to the control plane with a NetworkPolicy, and review its RBAC.

Deliverable: A table of C1 to C4 with exposure, immediate action, and the longer-term change, plus a list of every admission webhook in your clusters and who can reach it.

Scope: The fixture simplifies cluster networking. How the API server reaches webhooks, and which address range a policy must allow, differs between self-managed and managed clusters; verify it before enforcing a policy.

Articles and teaching material: CC BY 4.0. Original executable code: MIT. Attribution: Software Security.

## Sources

- [Kubernetes blog: Ingress-nginx CVE-2025-1974, what you need to know](https://kubernetes.io/blog/2025/03/24/ingress-nginx-cve-2025-1974/). 24 March 2025, links revised May 2025.
- [kubernetes/kubernetes issue 131009: CVE-2025-1974, ingress-nginx admission controller RCE escalation](https://github.com/kubernetes/kubernetes/issues/131009). 23 March 2025.
- [kubernetes/kubernetes issue 131006: CVE-2025-24514, configuration injection via the auth-url annotation](https://github.com/kubernetes/kubernetes/issues/131006). 23 March 2025.
- [ingress-nginx release controller-v1.12.1](https://github.com/kubernetes/ingress-nginx/releases/tag/controller-v1.12.1). 25 March 2025 (UTC).
- [Kubernetes blog: Ingress NGINX retirement, what you need to know](https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/). 11 November 2025.
- [Kubernetes documentation: Dynamic admission control](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/)
- [OWASP Kubernetes Top 10 (2025)](https://kubernetes-top10.owasp.org/). 2025 edition.
- [Wiz (third-party research, discoverer): IngressNightmare, CVE-2025-1974 in Kubernetes](https://www.wiz.io/blog/ingress-nginx-kubernetes-vulnerabilities). 24 March 2025.