【漏洞分析】【CTF】Wiz Kubernetes CTF(K8s LAN Party)Writeup
入口
Challenge 1 - Recon

这道题的目的是想让你找到隐藏在K8s集群内的其它服务。
大致实验过程如下:
查询当前容器的ip:

发现 是 192.168.11.169/31,说明主机只可能是192.168.11.168和192.168.11.169,既然192.168.11.169是当前容器的网络地址,那就说明192.168.11.168是广播地址,说明这个网段没有别的服务。但还是不放心,于是用dnscan先探查下,果然没结果:

查看是否在kubernetes内,有的话确认下kubernetes API Server的地址:

发现确实是在kubernetes集群中,且kubernetes API Server的地址是:https://10.100.0.1
。
接着继续信息收集,先后查看了/etc/hosts、/etc/hostname文件,但内容都是空的。
查看DNS配置文件/etc/resolve.conf,发现当前kubernetes集群的DNS服务器为:10.100.120.34。可以看出DNS服务器和Kubernetes API Server位于同一个B段。
所以 ,可以用dnscan探查下在这个B段里能不能找到其他的服务。
dnscan -subnet 10.100.0.0/16,成功在k8s集群中找到了另一个服务的IP和域名:

用curl直接访问该主机的80端口,得到flag:

Challenge 2 - Finding Neighbours

感觉跟Challenge 1很类似,也是发现在k8s集群里其他服务。
大致实验过程如下:



使用命令tcpdump -i ns-313861 dst host 10.100.171.123 and dst port 80 -w reporting-service.pcap监听网络通信,并将流量捕获到文件reporting-service.pcap:

使用命令tcpdump -r ./reporting-service.pcap -A查看数据包内容,可以看到flag在里面:

直接过滤下,tcpdump -r ./reporting-service.pcap -A |grep -i "wiz_k8s_lan_party"

PS:其实用dig -x <IP>进行DNS反向查找也是可以的,不一定要用它提供的dnscan命令,如下图 :
先通过netstat -pant查找网络连接情况,发现IP10.100.171.123

然后使用dig进行反向查找,发现对应的域名:

Challenge 3 - Data Leakage

这道题的题目说到,目标系统使用了一个过时的支持云的数据存储,但该技术是在仅支持网络级别的访问控制的时代引入的。
换言之,漏洞就在于访问控制,绕过它就能获取flag。
进入环境后,我还是跟前两道题一样,对10.100.0.1/16网段先做了其他服务发现的操作,但没有收获。
既然是存储,那就先看看有没有挂载到本地,执行df -hT查看:

发现目标,可以看到将远程AWS EFS存储挂载到了本地/efs目录,服务的主机域名是:fs-0779524599b7d5e7e.efs.us-west-1.amazonaws.com,对应的IP是192.168.124.98

查看下/efs目录,发现了flag文件:


不过我们无法查看flag文件的内容,因为它的权限位为000。
既然题目说了,访问控制是仅基于网络层面的,就自然而然不会是想着通过本地方式去读取文件。
于是笔者通过nfs+TAB键查看下系统内有没有一些疑似nfs的客户端工具,可以看到有如下这些命令:

显而易见的,nfs-cat就是我想要的,问题是该如何使用。
nfs-cat,目标环境没有该命令详细的帮助信息:

先凭自己的理解,尝试读取flag看看。但等了几十秒左右,结果报错如下:

而且在等待的过程中,用netstat -pant查看网络情况,发现很奇怪,nfs-cat连接的并不是目标的2049端口,竟然是111端口:

因为从GPT给我的信息来看,NFS服务的默认端口是2049端口。
难道还需要显式指定目标端口?这就有必要看看nfs-cat的帮助文档了。
既然在该CTF的环境里没有man手册,那就在自己的Ubuntu服务器上安装nfs-cat看看,如图:

可以看到,nfs-cat的man手册也很简略,但它指明了代码仓库http://github.com/sahlberg/libnfs:

详细说明应该在代码仓的帮助文件里有写。
从它的README文档可以了解到,该命令在使用时,如果不显示指定,则用的是NFS v3版本的协议,从CTF的环境里通过nfsstat -m或 mount |grep -i nfs,可以看到环境里NFS用的是v4版本:

所以需要在url里加上参数version=4指定版本,如下图:

可以看到结果不同了,但报错是意料之中,也就是没有权限访问的意思。
继续查看nfs-cat的README文档,发现竟然可以通过uid、gid指定以什么用户身份去访问:

从flag.txt文件的权限为可知,只有root用户才可以读。所以我们就在nfs url中指定uid=0,如下图,成功读取flag文件的内容:

后记
回头看下这道题的题目,一开始我用nfs-cat查看flag文件失败,总以为是要加上代理,或者是建立隧道才能绕过所谓的基于网络的访问控制策略。因为从题目来看,我以为是有IP白名单,也就是EFS服务设置了IP白名单策略。于是我一直在搜寻某个服务器存在代理端口的可能,又或是通过iptunnel这种工具在本机和目标EFS服务建立隧道。但经过一番折腾,都行不通。一是根本找不到也无法判断是否存在代理端口,因为存在Istio,所以使用nmap探测端口,都是open状态;二是iptunnel建立隧道需要在通信两端都进行设置才行。
从答案再看题目,我才有点理解了这里说的基于网络的访问控制的意思,在url指定uid参数去指定以某种用户角色权限去访问,这也确实算是基于网络的访问控制,就是在协议上加上参数去指定,也没毛病。
Challenge 4 - Bypass Boundaries

View Policy:

从题目的意思,大概是说,服务网格技术(典型的:Istio)对于(恶意的)root用户来说是很有吸引力的。
简单了解下什么是Istio:

从给出的AuthorizationPolicy策略来看,是不允许来自当前空间(k8s-lan-party)内的http get/post流量访问某个受保护的pod的,这个pod名给的是个占位符${flag-pod-name},大概率是如果绕过了该策略的防护,访问到该pod提供的服务,就能拿到flag。
用之前的dnscan -subnet 10.100.0.1/16获取到对应的flag pod的主机域名为istio-protected-pod-service.k8s-lan-party.svc.cluster.local,用curl访问,结果命中了前面的Istio的策略,无法获取flag:

想不到其他办法,在/etc/passwd查看到有两个普通用户,一个是istio,一个是player,因为这道题跟Istio有关,于是靠感觉,切换到了istio用户,再次用curl访问 ,bingo!竟然获取到了flag:


从Istio的github issue里找到了原因所在:
https://github.com/istio/istio/issues/4286
其实Istio的wiki里也写到了:
https://github.com/istio/istio/wiki/Understanding-IPTables-snapshot#use-pid-to-get-iptables
总结下就是说,uid=1337,这个是用于区分来自Envoy代理和应用程序的流量的 uid。以 1337 uid/gid 运行的应用程序不会被捕获,因此会绕过代理。
Challenge 5 - Lateral Movement

View Policy:

让GPT帮解读下这道题:


个人理解,意思就是这个Admission Control准入控制策略,会根据准入请求时作出相应的处理,这个处理的策略就是根据题目给出的policy文件制定的。即当发送Admission准入请求去在名为sensitive-ns的命名空间中创建或更新pod时,Admission Controller准入控制服务器就会往该Pod的容器里注入一个名为FLAG的环境变量。整个请求的流程如下图如下图:

另外,通过GPT的解读,我可以知道Admission Webhook通常包括MutatingAdmissionWebhook,而/mutate这个Endpoint就是用来处理Mutating Admission 请求的,可以在pod创建或更新时进行相应处理。

依旧使用dnscan工具进行内部服务发现:

经测试发现只有 kyverno-svc.kyverno.svc.cluster.local是开放web端口(443)并且存在/mutate 这个Endpoint的。
问题是请求数据如何构造,k8s官方文档关于 Dynamic Admission Control 并没有写具体怎么构造。
于是问GPT,试了很多它给的json数据,发了请求结果https://kvverno-svc.kyverno.svc.cluster.local/mutate 返回curl: (92) HTTP/2 stream 0 was not closed cleanly: INTERNAL_ERROR (err 2),搞的我以为我的解题方向错了,郁闷卡了挺久... 后来还是题目给的提示信息里知道 kube-review 可以实现yaml到Admission request请求数据的转换。
先在本地写好创建pod的yaml文件:
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: sensitive-ns
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
然后使用开源工具 kube-review 创建准入请求所需的json请求数据:
kube-review create pod.yaml > pod.json
然后将pod.json粘贴到pastebin,生成下载链接,就可以在CTF的shell环境下将pod.json文件下载下来了:

接着发送/mutate准入请求,响应数据里的patch,base64解码后可以看到就是策略里注入的FLAG:


综上,从这道题可以看到,Admission Controller可以随便让用户访问不是一件好事。
小结
学到了很多,其实云安全是个很有意思的领域,涉及新知识非常多,很有新鲜感.
另外,学习思路是一样的,你的知识面越广,对研究目标越熟悉,你能看到的攻击面越大。
通关后拿了个证书,当个纪念:
https://www.k8slanparty.com/certificate/lXIko99e

Reference
https://www.wiz.io/blog/k8s-lan-party-challenge
https://thegreycorner.com/2023/12/13/kubernetes-internal-service-discovery.html#kubernetes-dns-to-the-partial-rescue
https://gist.github.com/nirohfeld/c596898673ead369cb8992d97a1c764e
https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
https://github.com/istio/istio/wiki/Understanding-IPTables-snapshot#use-pid-to-get-iptables
https://github.com/istio/istio/issues/4286
https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#request
https://github.com/anderseknert/kube-review
浙公网安备 33010602011771号