Why the IngressNightmare Exploit Requires a Large Content-Length Header
The exploit deliberately sets a 1 MiB Content-Length header to force nginx to buffer the request body to a temporary file on disk, where the admission webhook later loads the malicious shared object.
The IngressNightmare vulnerability (CVE-2025-1974) leverages a precise technical detail about nginx's request handling to achieve remote code execution in Kubernetes clusters. According to the source code in esonhugh/ingressnightmare-cve-2025-1974-exps, the attack depends on declaring a large Content-Length that exceeds nginx's in-memory buffer threshold. This single configuration detail determines whether the malicious payload remains in memory or gets written to a temporary file accessible by the admission controller.
The Nginx Buffering Mechanism
Nginx processes incoming request bodies differently based on their declared size. When a request arrives with a Content-Length header, nginx compares the value against the client_body_buffer_size directive.
Requests smaller than this threshold remain entirely in memory. However, when the declared length exceeds this limit, nginx spills the request body to a temporary file on disk to avoid memory exhaustion. This behavior is standard for handling large uploads, but in the context of CVE-2025-1974, it creates a critical attack vector.
The exploit specifically targets this file-writing behavior. By forcing nginx to create a temporary file containing attacker-controlled data, the payload persists on the filesystem where the admission webhook can access it during configuration validation.
The 1 MiB Threshold in BadUploader
In nginx-ingress/exploit.go, the BadUploader function implements the upload phase of the attack. The code explicitly sets the content length to 1 MiB (1048576 bytes) to guarantee disk buffering:
contentLength := fmt.Sprintf("%v", 1024*1024) // 1 MiB
This value is deliberately chosen to exceed typical default values for client_body_buffer_size, which often default to 8 KB or 16 KB in standard nginx configurations. The function then constructs an HTTP POST request with this oversized header:
buffer := []byte(`POST ` + url.Path + ` HTTP/1.1\r\n` +
"Host: " + url.Host + "\r\n" +
"Content-Type: application/octet-stream\r\n" +
"Content-Length: " + contentLength + "\r\n" +
"Connection: keep-alive\r\n" +
"Accept: */*\r\n\r\n")
After sending these headers, the exploit transmits the malicious shared object (evil.so) as the request body. Because nginx believes it must buffer 1 MiB of data, it writes the payload to a temporary file in its filesystem rather than keeping it in memory.
From Temporary File to Arbitrary Code Execution
The temporary file creation is only the first stage. The repository's README.md explains the complete two-request attack flow:
“It is triggered by sending two request. One is a long buffered request to the NGINX server in same pod, then nginx will cache it as a temporary file.”
Once the temporary file exists, the second request targets the admission webhook. The webhook runs nginx -t (configuration test) with a crafted ssl_engine directive that points to the temporary file path. Because the file contains the attacker-supplied shared object, loading it as an SSL engine executes arbitrary code within the controller's security context.
Without the large Content-Length forcing the initial disk write, the payload would remain in memory and disappear when the upload connection closes, preventing the second stage from accessing the malicious library.
Exploit Implementation Details
The exploit ensures the payload reaches disk by padding it when necessary. In BadUploader, if the shared object is smaller than 8 KB, the code adds null byte padding:
if len(payload) < 8*1024 {
padding := bytes.Repeat([]byte{0x00}, 8*1024+10-len(payload))
payload = append(payload, padding...)
}
_, _ = conn.Write(payload)
This padding ensures the actual data transmitted is substantial enough to trigger nginx's file-buffering logic consistently. The combination of the declared 1 MiB Content-Length and the actual payload bytes creates the necessary conditions for the temporary file to persist until the admission webhook accesses it.
Summary
- The large Content-Length header (1 MiB) forces nginx to buffer the request body to disk rather than memory.
- This behavior is governed by the
client_body_buffer_sizedirective, with the exploit value exceeding typical defaults. - The temporary file persists the malicious shared object (
evil.so) where the admission webhook can access it. - The
BadUploaderfunction innginx-ingress/exploit.godeliberately sets this header to enable the second-stagessl_engineloading attack. - Without exceeding the memory buffer threshold, the payload would not be written to the filesystem, and the exploit would fail.
Frequently Asked Questions
Why is 1 MiB the chosen value for Content-Length?
The 1 MiB value reliably exceeds the default client_body_buffer_size in most nginx configurations, which typically ranges from 8 KB to 16 KB. While smaller values might work depending on the specific configuration, 1 MiB guarantees that nginx will write the request body to a temporary file regardless of minor configuration variations in target environments.
What happens if the Content-Length is smaller than the buffer size?
If the declared Content-Length is smaller than client_body_buffer_size, nginx keeps the entire request body in memory. When the connection closes, this memory is freed, and no temporary file is created on disk. Without the file on disk, the admission webhook cannot load the malicious shared object via the ssl_engine directive, and the exploit chain fails.
How does the admission webhook access the temporary file?
The admission webhook runs nginx -t with a crafted configuration that includes an ssl_engine directive pointing to the known temporary file path where nginx buffered the upload. The webhook executes this test as part of its validation process, inadvertently loading the attacker-controlled shared object and executing its code within the controller's security context.
Is this vulnerability specific to the ingress-nginx controller?
Yes, this specific exploit chain targets the ingress-nginx controller's architecture, where the nginx instance and admission webhook run in the same pod or share filesystem access. The vulnerability depends on the webhook being able to access nginx's temporary file directory, a configuration specific to how ingress-nginx handles admission requests and configuration validation.
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 →