xiaoxiong's blog Thinking and Action

ingress Nginx CVE-2025-1974分析

2025-07-25

简述

随着云原生技术的迅猛发展,其在企业数字化转型中的核心地位日益凸显,与此同时,云原生安全问题也受到了前所未有的关注。随着云原生架构的复杂度提升,漏洞的利用不再局限于单一组件的孤立缺陷,而是逐渐演变为多系统、多组件间的协同性风险

背景知识补充

通过背景知识补充,从全局视角了解风险

  • ingress 是什么,和云原生什么关系,有什么特点
  • ingress-nginx 与 ingress 有什么关系
  • ingress-nginx 组件介绍
  • 进行基本的使用和配置来了解组件如何使用

ingress 是什么

Ingress 是 Kubernetes 中的一种 API 资源,用于定义外部访问集群内服务的规则集合(主要针对 HTTP/HTTPS 流量)。它本身不直接处理流量,而是通过声明式规则描述 “如何将外部请求转发到集群内的服务”

简单概括就是 Ingress 是云原生应用暴露的 “标准化入口”

ingress 和 云原生什么关系

Ingress 作为 Kubernetes 官方定义的 API 资源 ,专门用于管理集群外部访问规则(如基于域名、路径、SSL 证书的路由)

Ingress 是必要组件么?

Kubernetes 中暴露服务的方式有多种(如 NodePortLoadBalancerClusterIP + 端口转发等),Ingress 是其中一种更灵活的方案。是否需要 Ingress 取决于业务场景:

  • 若仅需简单暴露单个服务(如开发环境临时访问),NodePortLoadBalancer(云环境)已足够,无需 Ingress;
  • 若需 多服务统一入口 (如通过同一域名 / IP 访问多个服务)、 基于路径 / 域名的精细路由 (如 api.example.com 转发到 API 服务,web.example.com 转发到前端服务)、 SSL 证书管理重定向 / 重写等复杂需求,Ingress 是更优选择。

特点

云原生强调 “声明式配置、动态适配、无状态化”,Ingress 完全符合这些特性:

  • 声明式定义 :Ingress 通过 YAML 配置文件声明流量规则(如 “example.com/api 路由到 api-service”),Kubernetes 控制器(如 Ingress-nginx)会自动同步规则并生效,无需手动操作,适配云原生 “基础设施即代码” 理念。
  • 动态适配 Pod 变化 :当后端 Pod 因扩缩容、故障重建而 IP 变化时,Ingress 控制器会通过 Service 自动感知后端端点变化,无需修改路由规则,适配云原生应用的动态性。
  • 与云原生生态工具集成 :Ingress 可与服务网格(如 Istio,通过 Gateway 资源扩展)、监控工具(如 Prometheus 采集流量 metrics)、日志系统(如 Fluentd 收集访问日志)等云原生组件无缝协作,形成完整的流量治理闭环。

ingress-nginx 与 ingress 有什么关系

Ingress 控制器是 Kubernetes 中负责解析和执行 Ingress 资源规则的组件,不同实现基于不同的代理 / 网关技术,ingress-nginx 属于 其中的一种技术

  1. ingress-nginx 基于 Nginx 反向代理实现的 Ingress 控制器,是 Kubernetes 社区最广泛使用的实现之一。轻量、稳定,支持基本的 HTTP/HTTPS 路由、SSL 终止、路径重写、负载均衡等功能,配置方式贴近 Nginx 原生语法。
  2. Traefik 一款云原生的反向代理和负载均衡器,原生支持 Kubernetes Ingress,特点是 动态配置自动发现 (无需重启即可加载新规则),集成了监控、TLS 自动配置(Let’s Encrypt)等功能,适合动态性强的环境。
  3. HAProxy Ingress 基于 HAProxy 实现的 Ingress 控制器,以高性能和稳定性著称,支持更复杂的负载均衡策略(如源地址哈希、权重分配),适合对性能要求较高的场景。
  4. Kong Ingress Controller 基于 Kong 网关(OpenResty 扩展)实现,除了基本的 Ingress 功能,还提供 API 管理能力 (如限流、认证、监控),适合需要对 API 进行精细化管控的场景。
  5. 云厂商专属实现
    • AWS ALB Ingress Controller:将 Ingress 规则转换为 AWS Application Load Balancer(ALB)的配置。
    • GKE Ingress Controller:Google Kubernetes Engine 内置的 Ingress 控制器,基于 GCP 的负载均衡服务。
    • Azure Application Gateway Ingress Controller:对接 Azure 的 Application Gateway。
  6. 其他实现 如 Nginx Ingress Controller(与 ingress-nginx 类似,但维护方不同)、Ambassador(基于 Envoy)、Contour(基于 Envoy)等。

Kong 和 envoy 以前都通过漏洞或多或少的了解过一些

ingress-nginx 组件介绍

ingress-nginx 主要包含以下几个核心组件:

  1. Controller(控制器)

这是最核心的组件,负责监听 Kubernetes Ingress 资源的变化,并动态生成和管理 NGINX 配置,实现流量的路由和负载均衡。

  1. Admission Webhook(准入控制器)

用于在 Ingress 资源创建或更新时进行校验和默认值填充,保证配置的正确性和安全性。通常以 sidecar 或独立 Deployment 方式运行。

  1. Default Backend(默认后端)

当请求未命中任何 Ingress 规则时,流量会被转发到 default backend,通常用于返回 404 或自定义错误页面。

  1. Admin API(管理接口)

某些版本/部署方式下,ingress-nginx 提供了 admin API,用于动态管理、查询和调试 NGINX 状态(如 /healthz、/metrics、/nginx_status 等)。

  1. Metrics Exporter(指标导出器)

集成了 Prometheus 格式的 metrics endpoint,便于监控 NGINX 的流量、状态和性能。

  1. Cert-Manager Webhook(证书管理集成)

用于自动化 TLS 证书的申请、续期和挂载,通常与 cert-manager 配合使用。

  1. Lua/Plugin 扩展

支持通过 Lua 脚本或插件机制扩展 NGINX 的功能,比如自定义认证、日志、流量控制等。

  1. 工具/辅助组件
  • kubectl 插件(如 charts/ingress-nginx/cmd/plugin/):用于命令行管理和调试。
  • 调试工具(如 cmd/dbg/):便于开发和排查问题。
  • 配置生成器(如 cmd/annotations/):自动生成注解文档或配置。

漏洞摘要说明

漏洞 CVE-2025-1974是由Wiz团队发现的一个Ingress-nginx组件漏洞,CVSS评分高达9.8。用户在默认配置情况下,拥有pod权限可访问到ingress nginx 的webhook 逻辑,在无需ingress nginx 授权情况下可触发恶意指令,导致任意代码执行

  • 补丁 commit https://github.com/kubernetes/ingress-nginx/commit/626305229ffa800e226f33f1396bcb164be099ed
  • 存在风险的版本
    • < v1.12.1
    • < v1.11.5

漏洞分析

  • 组件框架逻辑
  • 任意文件上传
  • ingress 配置注入

组件框架逻辑

这里可以看懂为什么可以未授权访问webhook服务

ingress-nginx 是由go 语言开发。内部多个端口均使用 net/http 做为基本的web框架,准确的说该pod内起了多个web服务

端口说明 配置

内部管理端口

  1. 健康检查端口 - 默认 10254

    • 用于健康检查端点
    • 可通过 –healthz-port 参数配置
  2. 状态端口 - 默认 10246

    • 用于 Lua HTTP 端点配置
    • 可通过 –status-port 参数配置
  3. 流端口 - 默认 10247

    • 用于 Lua TCP/UDP 端点配置
    • 可通过 –stream-port 参数配置
  4. 默认服务器端口 - 默认 8181

    • 用于暴露默认服务器(catch-all)
    • 可通过 –default-server-port 参数配置
  5. 性能分析端口 - 默认 10245

    • 用于 Go 性能分析器
    • 可通过 –profiler-port 参数配置

Webhook 相关端口

  1. Admission Webhook 端口 - 默认 8443
    • 用于 Kubernetes 准入控制器 webhook
    • 在 Helm chart 中通过 controller.admissionWebhooks.port 配置

deploy.yaml 配置

在ingress-nginx 提供的多个deploy.yaml 都声明了 Service ingress-nginx-controller-admission,其中有个port 映射关系为 https-webhook

1.png

端口 443 ,映射到容器内部的webhook 端口

2.png

所以访问 https://ingress-nginx-controller-admission.ingress-nginx.svc:443 能够访问到webhook 端口

webhook 服务

在 main 函数中有一段 webhook 启动函数

mc.Start(conf.ValidationWebhook)

默认配置如下

3.png

处理逻辑见 main.go#HandleAdmission 函数

目标意图只有一个 ia.Checker.CheckIngress(&ingress) 检测ingress 规则。

  • 在这里只是检测规则,所以可未授权访问
  • nginx controller 会一直监听 k8s api server 中 ingress 规则的实现,修改ingress 规则。所以能够修改ingress 规则,同理也可以攻击ingress nginx 服务

任意文件上传

任意文件上传利用了linux 临时文件特性

nginx 缓存

Nginx 的 client_body_buffer_size 指令用于设置客户端请求体(如 POST 数据)的内存缓冲区大小。

当请求体大小 > 缓冲区大小时:超出部分会写入临时文件,存储路径由 client_body_temp_path 指令指定(若未配置则使用默认路径)。

以前看过缓存命名

http {
    client_body_temp_path /data/nginx/tmp/body 1 2;
    # 最后两个数字表示子目录层级(1级目录+2级子目录,用于分散文件)
}

从nginx缓存角度出发,这是一个难以被遍历/猜测/枚举的值

当请求体需写入临时文件时,Nginx 生成的临时文件命名遵循固定格式:

[数字].[进程ID].[序列号]

例如:0000000001.0000000123.0000000456

第一部分:请求相关的编号(递增)

第二部分:Nginx 工作进程的 PID(进程 ID)

第三部分:临时文件的序列号(同一请求可能拆分多个文件时使用)

这些文件存储在 client_body_temp_path 配置的目录及其子目录中(根据配置的层级分散存储,如 …/body/1/23/0000000001…)

linux 文件描述符

在 Linux 系统中,/proc/self/fd/ 目录下的文件描述符(FD)命名规则非常简单直接,遵循以下特性:

  1. 命名格式 : 以纯数字作为文件名,直接对应进程持有的文件描述符编号。 例如:/proc/self/fd/0/proc/self/fd/1/proc/self/fd/5 等。
  2. 编号规则
  • 文件描述符是从 0 开始的非负整数,按进程打开文件的顺序递增分配。
  • 系统默认占用前 3 个描述符:
    • 0:标准输入(stdin)
    • 1:标准输出(stdout)
    • 2:标准错误(stderr)
  • 后续打开的文件(包括普通文件、管道、网络套接字等)会依次分配 345…… 等编号。
  1. 本质是符号链接 : 这些数字文件名实际上是符号链接,指向该描述符对应的实际文件或资源。例如:
  • lrwx------ 1 root root 64 7月 26 10:00 5 -> /var/lib/nginx/body/0000000001.0000000123.0000000456 表示描述符 5 指向 Nginx 生成的某个客户端请求体临时文件。
  1. 动态性 : 当文件被关闭后,对应的描述符编号会被释放,可被后续打开的文件重复使用(但不会自动删除 /proc/self/fd/ 中的符号链接,而是显示为 (deleted))。

总结来说,/proc/self/fd/ 下的文件描述符命名仅以数字编号标识,与文件本身的名称、类型无关,仅反映进程内部对文件的引用编号。

这里就存在爆破的可能性了

维持长时间http连接

在这里通过将 Content-Length 设置大于原文件大小的数值,并设置 Content-Type为流式数据

上述命令中,设置了 Content-Length为大于原文件大小的数值,并设置 Content-Type为流式数据,这样nginx就会持续等待发送更多数据,从而导致进程挂起,文件描述符fd持续处于开启状态。

TODO

从漏洞利用角度来说这里很精彩,但需要注意一个点

  • 文件上传缓存是通过nginx 完成
  • 配置文件加载触发是由ingress-nginx-controller 完成

属于不同的进程,所以没法使用 /proc/self/fd , 还需要枚举进程号

再关注其他中间件 与 linux。也存在一些临时文件及缓存处理。

注意生成缓存的进程,对于php 与 nginx 配合组合,常因为进程权限不同无法加载。但对于tomcat ,直接利用中间件缓存 /proc/self 可以避免权限问题

参考文章

配置注入

这里使用了go 配置框架 text/template 模板

上手写了下demo 测试

func dollarVariableTemplateExample() {
	fmt.Println("6. $变量作用域示例:")

	type Server struct {
		ServerName string
		Locations  []*Location
	}

	apiRaw := "http://auth-service:3000/verify#;}}}\n\nssl_engine /test/custom_path;\n\n"
	adminRaw := "http://admin-auth:3001/verify"

	server := Server{
		ServerName: "example.com",
		Locations: []*Location{
			{
				Path:        "/api",
				ServiceName: "api-service",
				ServicePort: 8080,
				ExternalAuth: &ExternalAuth{
					URL:    apiRaw,
					Method: "api-host",
				},
			}
		},
	}

	tmplText := `
server {
    server_name ;
    
    location  {
        proxy_pass http://:;
        
        # 外部认证配置
        set ;
        set ;
        
    }
    
}`
	tmpl, err := template.New("dollar").Funcs(template.FuncMap{
		"quote": quote,
	}).Parse(tmplText)
	if err != nil {
		fmt.Printf("模板解析错误: %v\n", err)
		return
	}

	var buf bytes.Buffer
	err = tmpl.Execute(&buf, server)
	if err != nil {
		fmt.Printf("模板执行错误: %v\n", err)
		return
	}

	fmt.Println(buf.String())
	fmt.Println()
}

可以发现通过 双引号 闭合导致模板注入发生。(这里我理解是go语言模板库未提供安全框架)

修复使用自定义 quote 函数 和 strconv.Quote 进行转义

在漏洞利用处,通过配置注入,注入 ssl_engine 缓存so 文件,利用 nginx 配置测试时 ,加载恶意so文件,造成命令执行

修复

4.png 5.png

修复

整个漏洞完整的补丁见

commit https://github.com/kubernetes/ingress-nginx/commit/626305229ffa800e226f33f1396bcb164be099ed

最重要的修复是临时删掉了 nginx 配置测试加载,很干脆。


	/* Deactivated to mitigate CVE-2025-1974
	// TODO: Implement sandboxing so this test can be done safely
	err = n.testTemplate(content)
	if err != nil {
		n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
		return err
	}
	*/

后利用问题,

ingress-nginx 具备 clusterRole 权限具备所有配置可读权限。

# ClusterRole 权限
- apiGroups: [""]
  resources: ["configmaps", "endpoints", "nodes", "pods", "secrets", "namespaces"]
  verbs: ["list", "watch"]

Comments