3. 数据泄露场景一:指标(Metrics)标签泄露——错误地将租户ID暴露在公共标签中
3.1 场景描述
在多租户后端系统中,指标(Metrics)通常由 Prometheus、VictoriaMetrics 或 OpenTelemetry 等系统采集。当运维人员或开发者在定义指标标签(Label)时,无意中将租户 ID(tenant_id)作为公共标签附加到指标上,导致所有能够查询该指标的用户(包括其他租户)都能通过标签值枚举或推断出系统中存在的租户列表及其活动模式。
⚠️ 风险等级:高 — 租户 ID 属于敏感元数据,一旦暴露,攻击者可利用其发起租户枚举攻击、业务量分析或定向渗透。
3.2 泄露原理
指标系统通常允许用户通过标签进行聚合、过滤和分组。如果租户 ID 被设置为标签,则任何拥有指标查询权限的实体都可以执行类似 count by (tenant_id) 的查询,从而获取所有租户的 ID 列表。
| 组件 | 正常行为 | 泄露行为 |
|---|---|---|
| 指标定义 | 使用内部标签(如 instance、job) | 将 tenant_id 作为公开标签 |
| 查询接口 | 只能看到自身租户的数据 | 可通过标签查询枚举所有租户 |
| 数据存储 | 租户数据物理或逻辑隔离 | 所有租户指标混存,标签成为唯一隔离手段 |
3.3 典型泄露路径
- 应用代码直接暴露:在业务逻辑中,将请求上下文中的
tenant_id直接作为 Prometheus 指标标签。 - 中间件自动注入:API 网关或服务网格自动将请求头中的租户信息注入到指标中。
- 日志转指标:从日志中提取租户 ID 并转换为指标标签,未做脱敏处理。
- 自定义 Exporter:开发者在编写自定义 Exporter 时,为了方便调试,将租户 ID 作为标签输出。
🔍 排查要点: 检查所有自定义指标的定义代码,搜索
tenant_id、customer_id、org_id 等字段是否出现在 Labels() 或 WithLabelValues() 调用中。
3.4 泄露示例(代码层面)
以下是一个典型的错误实现,将租户 ID 直接暴露在指标标签中:
// ❌ 错误示例:将 tenant_id 作为公共标签
var requestCounter = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "endpoint", "tenant_id"}, // tenant_id 暴露
)
func handler(w http.ResponseWriter, r *http.Request) {
tenantID := r.Header.Get("X-Tenant-ID")
requestCounter.WithLabelValues(r.Method, r.URL.Path, tenantID).Inc()
// ...
}
正确的做法是将租户 ID 从标签中移除,或使用内部标签系统(如通过指标名称前缀隔离):
// ✅ 正确示例:使用指标名称前缀隔离租户
var requestCounter = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "endpoint"}, // 不包含 tenant_id
)
func handler(w http.ResponseWriter, r *http.Request) {
// 租户隔离通过指标命名空间或查询时过滤实现
requestCounter.WithLabelValues(r.Method, r.URL.Path).Inc()
}
3.5 攻击者视角:如何利用泄露的租户 ID
- 租户枚举:通过
count by (tenant_id)获取所有活跃租户 ID。 - 业务量分析:按租户聚合请求量、错误率,推断竞争对手或高价值客户。
- 定向攻击:结合其他泄露信息,对特定租户发起 DDoS 或漏洞利用。
- 合规风险:在金融、医疗等场景下,租户 ID 暴露可能违反 GDPR、HIPAA 等法规。
3.6 隔离方案对比
| 方案 | 隔离粒度 | 标签泄露风险 | 运维复杂度 |
|---|---|---|---|
| 标签隔离(错误) | 粗粒度 | 高 | 低 |
| 指标名称前缀隔离 | 中粒度 | 中 | 中 |
| 独立指标实例(每个租户一个端点) | 细粒度 | 低 | 高 |
| 查询时过滤(结合认证) | 细粒度 | 低 | 高 |
3.7 架构视角:标签泄露的传播路径
下图展示了租户 ID 从应用代码泄露到指标系统的完整路径:
3.8 检测与修复
| 阶段 | 检测方法 | 修复措施 |
|---|---|---|
| 代码审查 | 搜索所有指标定义中的标签列表,检查是否包含租户相关字段 | 移除租户 ID 标签,改用指标名称前缀或内部标签 |
| 运行时监控 | 对指标系统执行 count by (tenant_id) 查询,检查是否返回多个租户 |
配置指标采集器进行标签白名单过滤 |
| 安全扫描 | 使用 SAST 工具扫描代码中的指标定义 | 建立指标标签规范,禁止敏感字段作为标签 |
| 权限审计 | 检查指标查询接口的访问控制策略 | 实施租户级别的查询权限隔离 |
💡 最佳实践: 在指标系统中,永远不要将租户 ID、用户 ID、邮箱等个人身份信息(PII)作为标签。如果需要按租户隔离,应使用指标名称前缀(如
tenant_abc_http_requests_total)或部署独立的指标实例。
3.9 总结
指标标签泄露是最容易被忽视的数据泄露场景之一,因为开发者往往只关注指标的功能正确性,而忽略了标签的可见性。租户 ID 一旦作为公共标签进入指标系统,就会成为攻击者枚举租户、分析业务模式的突破口。修复的核心原则是:标签只用于描述技术维度(如 method、endpoint、status_code),不用于标识业务实体(如 tenant_id、user_id)。