免责声明
道一安全(本公众号)的技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。本文所提供的工具仅用于学习,禁止用于其他!!!
前言
在这篇博文中,我们分享了从发现 IngressNightmare 中获得的关键经验,IngressNightmare 影响了 Kubernetes 的 Ingress NGINX 控制器的准入控制器组件。根据我们的分析,大约 43% 的云环境容易受到这些漏洞的影响,我们的研究发现,包括财富 500 强公司在内的 6,500 多个集群将易受攻击的 Kubernetes 入口控制器的准入控制器公开暴露在公共互联网上,使它们面临直接的严重风险。
我们建议尽快修补。此博客文章详细介绍了漏洞的技术要素,并包含面向防御者的缓解和检测指南。
什么是适用于Kubernetes的Ingress NGINX控制器?
Ingress-NGINX 在 Kubernetes 文档中明确突出显示为一个示例 Ingress 控制器,它满足在 Kubernetes 中使用 Ingress 的先决条件。我们的研究表明,超过 41% 的面向互联网的集群正在运行 Ingress-NGINX。
漏洞
当 Ingress-NGINX 准入控制器处理传入的 Ingress 对象时,它会从该对象构建 NGINX 配置,然后使用 NGINX 二进制文件对其进行验证。我们的团队在此阶段发现了一个漏洞,该漏洞允许通过网络直接向准入控制器发送恶意入口对象,从而远程注入任意 NGINX 配置。
在配置验证阶段,注入的 NGINX 配置会导致 NGINX 验证器执行代码,从而允许在 Ingress NGINX Controller 的 Pod 上远程执行代码 (RCE)。
准入控制器提升的权限和不受限制的网络可访问性创建了关键的升级路径。利用此缺陷,攻击者可以执行任意代码并跨命名空间访问所有集群密钥,这可能导致完全接管集群。
缓解与检测
首先,确定您的集群是否在使用 ingress-nginx。在大多数情况下,您可以通过使用集群管理员权限运行 kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx
来检查这一点。
该漏洞已在 Ingress NGINX Controller 版本 1.12.1 和 1.11.5 中修复。我们强烈建议集群管理员:
-
更新到最新版本的 Ingress NGINX Controller。
-
确保准入 Webhook 端点未在外部公开 。
-
您可以使用此 Nuclei 模板来检查公开的 Ingress-NGINX 准入控制器。
如果您无法立即升级,请考虑以下缓解措施之一:
-
实施严格的网络策略 ,以便只有 Kubernetes API 服务器可以访问准入控制器。
-
如果您无法立即升级, 请暂时禁用 Ingress-NGINX 的准入控制器组件。
-
如果您已使用 Helm 安装了 ingress-nginx,请使用
controller.admissionWebhooks.enabled=false
重新安装。 -
如果您手动安装了 ingress-nginx,请删除调用的
ValidatingWebhookConfiguration
ingress-nginx-admission
,并从ingress-nginx-controller
容器的 Deployment 或 DaemonSet 中删除--validating-webhook
参数。 -
请记住在升级后重新启用 Validating Admission Controller,因为它为您的 Ingress 配置提供了重要的保护措施。
Wiz 客户可以使用 Wiz 威胁中心中预先构建的查询和建议。Wiz 还使用 Wiz Dynamic Scanner 验证公开的准入控制器。最后,Wiz Runtime Sensor 通过持续监控入口流量、实时捕获恶意准入审查请求并标记异常库加载来防止类似攻击,从而检测 IngressNightmare 等零日漏洞。
我们是如何发现IngressNightmare的?
Kubernetes 准入控制器在 Kubernetes 环境中提供了一个有趣且经常被忽视的攻击面。它们由 Kubernetes API 服务器触发,以便在处理请求之前审查并可能修改或阻止请求 (AdmissionReview), 并且它们通常在集群内以相对较高的权限运行。准入控制器通常不需要身份验证,本质上充当 Web 服务器,在集群中引入了一个额外的内部网络可访问终端节点。这种架构允许攻击者直接从网络中的任何 Pod 访问它们,从而显著增加攻击面。
适用于 Kubernetes 的 Ingress NGINX 控制器的背景
Ingress NGINX Controller 是一种 Ingress 实现,它使用 NGINX 作为反向代理和负载均衡器。它是最受欢迎的入口之一,也是 Kubernetes 的核心项目。
为了在 Kubernetes 和 NGINX 配置之间架起桥梁(这是一种非 Kubernetes 原生技术),控制器会尝试将 Kubernetes Ingress 对象转换为 NGINX 配置。为了确保 NGINX 服务器的稳定性,控制器使用验证准入 webhook,在应用最终配置之前对其进行验证。
从攻击者的角度来看,准入控制器是一个未经身份验证的 HTTP 端点,负责复杂的作,默认情况下,它与 Kubernetes 角色一起运行,该角色允许访问环境的所有机密,使其成为一个有吸引力的研究目标。
远程 NGINX 配置注入
在审查 Ingress NGINX 准入控制器代码时,我们发现了一个有趣的代码路径:当它处理传入的 AdmissionReview 请求时,它会根据模板文件和提供的 Ingress 对象生成一个临时的 NGINX 配置文件。然后,它使用 nginx -t
命令测试临时配置文件的有效性。我们发现了多种在此代码路径中注入新配置指令的方法。
// testTemplate checks if the NGINX configuration inside the byte array is valid
// running the command "nginx -t" using a temporal file.
func(n *NGINXController) testTemplate(cfg []byte) error {
...
tmpfile, err := os.CreateTemp(filepath.Join(os.TempDir(), "nginx"), tempNginxPattern)
...
err = os.WriteFile(tmpfile.Name(), cfg, file.ReadWriteByUser)
...
out, err := n.command.Test(tmpfile.Name())
func(nc NginxCommand) Test(cfg string) ([]byte, error) {
//nolint:gosec // Ignore G204 error
return exec.Command(nc.Binary, "-c", cfg, "-t").CombinedOutput()
}
通常,只有 Kubernetes API 服务器应该发送这些 AdmissionReview 请求。但是,由于 Admission Controller 缺少身份验证,因此具有最小网络访问权限的攻击者可以从集群内的任何 Pod 构建并发送任意 AdmissionReview 请求。
在我们的测试中,我们使用 kube-review 从 Ingress 资源清单创建准入审查请求,然后可以通过 HTTP 直接发送到准入控制器。
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1",
"request": {
"uid": "732536f0-d97e-4c9b-94bf-768953754aee",
...
"name": "example-app",
"namespace": "default",
"operation": "CREATE",
...
"object": {
"kind": "Ingress",
"apiVersion": "networking.k8s.io/v1",
"metadata": {
"name": "example-app",
"namespace": "default",
...
"annotations": {
"nginx.ingress.kubernetes.io/backend-protocol": "FCGI"
}
},
"spec": {
"ingressClassName": "nginx",
"rules": [
{
"host": "app.example.com",
"http": {
"paths": [
{
"path": "/",
"pathType": "Prefix",
"backend": {
"service": {
"name": "example-service",
"port": {}
}
}
}
]
}
}
]
},
...
}
}
图:Admission Review 对象示例
从上面可以看出,我们可以控制很多字段,显示出较大的攻击面。在这篇博文中,我们将介绍注释解析器中的两个漏洞,这些漏洞解析了上述请求中的 .request.object.annotations
字段。此字段中的属性稍后包含在 NGINX 配置文件中 - 我们使用它来注入任意指令。
CVE-2025-24514 – auth-url 注释注入
authreq 解析器负责处理与身份验证相关的 Comments。在以下代码流中,该注释需要设置 auth-url 字段,其中包括一个 URL,并最终传播到配置文件中:
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
}
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 }}
nginx.ingress.kubernetes.io/auth-url: "http://example.com/#;ninjection_point"
...
proxy_http_version 1.1;
set $target http://example.com/#;
injection_point
proxy_pass $target;
...
该漏洞不适用于 v1.12.0。在这个版本中,Ingress NGINX Controller 更改了其默认安全设置 ,以根据严格的正则表达式规则验证所有注释,包括 auth-url。
CVE-2025-1097 – auth-tls-match-cn 注释注入
authtls 解析器的auth-tls-match-cn 注解使用 CommonNameAnnotationValidator 来验证字段值:
funcCommonNameAnnotationValidator(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
}
换句话说,auth-tls-match-cn 注释需要:
1.该值必须以 CN= 开头。
2.所有剩余字符必须构成有效的正则表达式。
与之前的注入类似, $server.CertificateAuth.MatchCN
对应于 auth-tls-match-cn
注解的值。虽然很棘手,但我们仍然可以绕过这两个要求,在模板的这一部分注入任意的 NGINX 配置:
if ( $ssl_client_s_dn !~ {{ $server.CertificateAuth.MatchCN }} ) {
return 403 "client certificate unauthorized";
}
auth-tls-match-cn
注释:nginx.ingress.kubernetes.io/auth-tls-match-cn: "CN=abc #(n){}n }}nglobal_injection;n#"
...
set $proxy_upstream_name "-";
if ( $ssl_client_s_dn !~ CN=abc #(
){} }}
global_injection;
# ) {
return 403 "client certificate unauthorized"; }
...
auth-tls-match-cn
注释值显示在配置中,我们还需要提供 nginx.ingress.kubernetes.io/auth-tls-secret
注释,该注释对应于集群中存在的 TLS 证书或密钥对密钥。由于 Ingress NGINX 使用的服务帐户可以访问集群中的所有 Secret,因此我们可以指定任何命名空间中的任何 Secret 名称,只要它与所需的 TLS 证书/密钥对格式匹配即可。值得注意的是,许多托管的 Kubernetes 解决方案默认包含此类密钥。以下是可用于此类攻击的常见密钥的简短列表: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
CVE-2025-1098 – 镜像 UID 注入
在镜像
注释解析器中, 以下代码处理 ingress 对象的 UID,并将其插入 $location 中。Mirror.Source 的 Mirror.Source 的 Mirror.Source 的 Mirror.Source
中。我们控制着 ing。UID
字段,该字段允许新的注入点。
由于此注入位于 UID 参数中,而不是 Kubernetes 注解,因此我们的输入不会被注解的正则表达式规则清理。由于我们的输入是按原样插入的,因此我们可以轻松转义上下文并注入任意 NGINX 配置指令。
CVE-2025-1974 - NGINX配置代码执行
nginx -t
进行测试。这不会立即导致代码执行。如果我们能在 nginx -t
中找到执行任意代码的指令,它将破坏 Pod 并获得其高特权的 Kubernetes 角色。需要注意的是,NGINX 配置只是在测试中,并没有被应用,从而减少了我们实际可以(滥用)使用的指令数量。图:可用 NGINX 指令的部分列表( 来源 )
最初我们尝试使用 load_module
指令 ,它可以从文件系统加载共享库。但是,它只能在 NGINX 配置的开头使用,因此在注入时,load_module
将失败,并显示以下错误消息:
Ingress NGINX Controller 中有许多可用的指令, 因为它们的 NGINX 实例是使用许多附加模块编译的 。我们发现 ssl_engine
指令是 OpenSSL 模块的一部分,也可以加载共享库。此行为未记录在案。与 load_module
不同,此指令可以在配置文件中的任何位置使用 ,因此它适用于我们注入的约束。
我们现在可以在 NGINX 配置测试阶段加载任意库文件。我们的下一个挑战是:如何将共享库放在 Pod 的文件系统上?
使用 NGINX 客户端 Body 缓冲区上传共享库
与 nginx -t
和准入控制器 webhook 并行,Pod 还运行 NGINX 实例本身,侦听端口 80 或 443:
在处理请求时,NGINX 有时会将请求体保存到临时文件中( 客户端体缓冲 )。如果 HTTP 请求正文大小大于特定阈值( 默认为 8KB),则会发生这种情况。这意味着理论上我们应该能够发送一个大型 (>8KB) HTTP 请求,其中包含共享库形式的有效负载作为请求的正文,NGINX 会将其临时保存到 Pod 文件系统上的文件中。在 Pod 的文件系统上。
不幸的是,NGINX 也会立即删除该文件 ,从而产生几乎不可能的争用条件。但是,NGINX 持有一个指向该文件的打开文件描述符,可以从 ProcFS 访问该文件:
为了保持文件描述符打开,我们可以将请求中的 Content-Length
标头设置为大于实际内容大小。NGINX 会一直等待发送更多数据,这会导致进程挂起,让文件描述符打开的时间更长。
这个技巧的唯一缺点是我们在不同的进程中创建文件,因此我们不能使用 /proc/self
来访问它。相反,我们将不得不猜测 PID 和 FD 编号才能找到共享库,但由于这是一个进程最少的容器,因此只需几次猜测即可相对较快地完成此作。
从配置注入到RCE
该漏洞的工作原理如下:
-
滥用 NGINX 的 client-body 缓冲区功能,将我们的 payload 以共享库的形式上传到 Pod
-
向 Ingress NGINX 控制器的准入控制器发送 AdmissionReview 请求,其中包含我们的任意一个指令注入
-
我们注入的指令是
ssl_engine
指令,它会导致 NGINX 将指定的文件作为共享库加载 -
我们指定有效负载的文件描述符的 ProcFS 路径
-
如果一切顺利,我们的共享库现在已加载,我们远程执行代码
以下是实际漏洞利用的演示:
https://vimeo.com/1068882440
原文链接:https://www.wiz.io/blog/ingress-nginx-kubernetes-vulnerabilities#how-did-we-discover-ingressnightmare-23
本公众号仅做翻译转载
点分享
点收藏
点在看
点点赞
原文始发于微信公众号(道一安全):Kubernetes 集群安全警报:Ingress NGINX 高危漏洞或致集群全面沦陷
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论