etcd Watch 授权绕过漏洞分析:鉴权检查与请求归一化的顺序问题
漏洞概述
- 漏洞编号:GO-2026-6114
- 影响组件:
go.etcd.io/etcd/server/v3 - 影响版本:3.5.33、3.6.14、3.7.1 之前
- 修复版本:3.5.33 / 3.6.14 / 3.7.1
- 风险类型:RBAC 授权绕过
- 前提条件:已认证用户,且仅对单个 key 拥有 READ 权限
核心问题:鉴权检查与请求归一化的顺序
直白地说:Watch RPC 处理器在鉴权检查之前,就把开放式范围(open-ended range)的末尾归一化成了“精确 key”。这个顺序错位导致权限评估逻辑(isWatchPermitted)拿到的请求看起来是一个“单 key watch”,于是按单 key 权限放行。但底层 watch 流实际订阅的是字典序大于等于目标 key 的整个范围。
WithFromKey 语义回顾
clientv3.WithFromKey() 表示创建一个“从某个 key 开始,到正无穷”的 watch 范围。即:
- watch 起点:传入的 key(包含)
- watch 终点:
\0之后所有 key,逻辑上无上限
正常来说,这类请求需要用户具备该范围的 READ 权限。但由于归一化在鉴权之前完成,服务端将范围末尾“折叠”成目标 key 本身,从而在授权检查阶段被误判为单 key 访问。
漏洞触发路径
- 用户通过 RBAC 只被授予
key "foo"的 READ 权限。 - 攻击者使用已认证身份发起 watch,key 设为
foo,并附加WithFromKey()。 - 服务端收到请求后,先对 range end 做归一化处理,使其成为单 key 形式。
- 鉴权器执行
isWatchPermitted,看到的是单 keyfoo,权限检查通过。 - 实际 watch 流开始推送所有
>= foo的 key 更新事件,远超授权范围。
结果是:单 key 读权限被放大成整段 key 读取。
影响评估
- 攻击者必须已通过 etcd 认证(非匿名)。
- 攻击者本身拥有至少一个 key 的只读权限。
- 绕过仅影响 watch 路径,不影响 raw range 或 txn 路径(至少本漏洞未涉及)。
- 敏感信息泄露:只要还有更高排序的 key,攻击者就能持续观察其变更。
升级建议
- 升级到 etcd server v3 3.5.33+(3.5 系列)
- 或 3.6.14+(3.6 系列)
- 或 3.7.1+(3.7 系列)
如果无法立即升级,临时缓解措施:
- 不要为单个 key 授予仅 READ 权限的独立用户,除非业务完全隔离。
- 对所有使用
WithFromKey()的客户端做代理层校验,确认其 RBAC 角色覆盖完整范围。 - 审查审计日志,寻找非预期的开放式 watch 动作。
运维自查清单
1. 是否启用 RBAC?
etcdctl auth status
如果返回 Authentication enabled: true,继续检查。
2. 是否存在“只读单 key”用户?
枚举用户和角色:
etcdctl user list
etcdctl role list
对每个角色,查看其权限:
etcdctl role get
重点关注形如:
RangeRead:
[key: "app/foo/secret"]
这类权限仅覆盖一个 key。如果该角色被分配给了真实用户,则存在被利用的潜在条件。
3. 是否有可疑的开放式 watch 请求
开启 etcd debug 日志,或通过 metrics 检查 watch 流数量、范围分布。若发现大量 range end 为 \0 或 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 的 watch,且发起者的角色只覆盖单 key,则需立即标记并排查。
4. 版本核验
etcd --version
确认是否低于修复版本。若低于 3.5.33 / 3.6.14 / 3.7.1,按上述升级计划处理。
一句话总结
鉴权前做请求归一化,等于让权限检查“看着单 key,放行走全表”。升级到指定版本,并重新梳理单 key 只读角色的必要性。