Grafana 源码泄露事件:一个 GitHub 令牌引发的供应链安全反思

2026-05-19 14 预计阅读时间:1 分钟
来源:oschina.net AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:11 分钟

Grafana Labs 近日在社交平台公开承认:有未经授权的第三方窃取了访问其 GitHub 环境的令牌,攻击者借此进入了 Grafana 的代码仓库。好消息是——经内部调查,没有客户数据或个人信息被触及,客户系统与运营也未受影响。Grafana 已定位到凭证泄露源头,注销了遭泄露的令牌并追加额外防护措施。更值得关注的态度是:Grafana 明确拒绝向黑客支付赎金。

一次令牌泄露,源码被拖走,公司选择不赎。这件事本身不算罕见,但把它放在供应链安全的语境下审视,值得每个依赖开源项目的团队认真对待。

令牌是怎么成为攻击入口的

GitHub 令牌(Personal Access Token、OAuth Token、Deploy Key 等)本质是一串凭据,拥有它就拥有对应权限范围内的仓库读写能力。Grafana 事件中,攻击者拿到的是一个能访问 GitHub 组织环境的令牌——这意味着不是某个个人仓库被攻破,而是组织级别的权限被滥用。

令牌泄露的常见路径包括:

  • 硬编码进代码或配置文件,随后被推到公开仓库或泄露到外部;
  • 存储在 CI/CD 环境变量中,但日志或调试输出意外暴露;
  • 员工终端被入侵,本地 .git-credentials 或环境变量被窃取;
  • 第三方集成过度授权,一个服务账号的令牌权限远超实际需要。

Grafana 表示已查明泄露源头,但未公开具体是哪条路径。无论细节如何,核心教训不变:令牌是高价值目标,一旦泄露,攻击者不需要漏洞利用、不需要零日,直接合法身份进入仓库。

源码被窃 ≠ 服务被攻破

Grafana 强调客户数据和个人信息未被访问,客户系统无影响。这背后有一个关键区分:源码泄露和服务被攻破是两件事

源码被窃取的风险主要集中在:

  1. 攻击者可以研究代码寻找未公开漏洞,为后续攻击做准备;
  2. 供应链信任受损——下游用户会质疑"我用的版本是否被篡改";
  3. 内部工具、脚本、架构细节暴露,降低攻击者的信息收集成本;
  4. 如果源码含硬编码密钥或内部端点,则可能直接引发二次泄露。

但如果项目本身有健全的构建与发布验证机制(签名校验、可复现构建),源码泄露并不等于交付物被污染。Grafana 能快速确认客户侧无影响,说明其发布流程有一定的完整性保障。

拒付赎金:理性选择还是姿态表态

Grafana 选择不付赎金。从安全行业共识看,这个决策有明确依据:

  • 付赎金不保证数据不被公开——攻击者没有契约约束,拿了钱照样可以泄露;
  • 付赎金激励后续攻击——一旦确认某目标会付钱,攻击者会重复瞄准;
  • 源码本身不具备"收回"属性——代码一旦被拖走,删除攻击者副本在技术上不可行,赎金换不来真正的"销毁"。

对开源项目而言,源码本就面向公众,被窃取的增量损失主要在内部工具和未公开分支上。拒付赎金是合理决策,但前提是团队必须有能力评估泄露的实际影响范围,并快速完成凭证轮换与加固。

令牌安全加固:可立即执行的检查与防护

以下是一组可以直接在 GitHub 组织中运行的检查与加固操作,适用于任何使用 GitHub 的团队。

1. 扫描组织内所有令牌与 Deploy Key

# 使用 GitHub CLI 列出组织中所有 fine-grained personal access tokens
# 需要组织管理员权限
gh api orgs/YOUR_ORG/personal-access-tokens \
  --jq '.[] | {id: .id, owner: .owner.login, repository_selection: .repository_selection, permissions: .permissions, expires_at: .expires_at}'

# 列出所有 OAuth Apps 的访问令牌(需要按 App 逐个查询)
gh api orgs/YOUR_ORG/apps \
  --jq '.[] | {id: .id, name: .name, client_id: .client_id}'

2. 审计仓库中的 Deploy Key

# 列出指定仓库的所有 deploy keys
gh api repos/YOUR_ORG/YOUR_REPO/keys \
  --jq '.[] | {id: .id, title: .title, key: .key[:40], read_only: .read_only, verified: .verified}'

# 批量扫描组织下所有仓库的 deploy keys
for repo in $(gh repo list YOUR_ORG --limit 100 --json name --jq '.[].name'); do
  echo "=== $repo ==="
  gh api repos/YOUR_ORG/$repo/keys \
    --jq '.[] | {title: .title, read_only: .read_only, verified: .verified}' 2>/dev/null
done

3. 用 git-secrets 防止令牌硬编码进代码

# 安装 git-secrets(AWS 开源,但规则可自定义)
git clone https://github.com/awslabs/git-secrets.git
cd git-secrets && make install

# 在已有仓库中启用
cd YOUR_REPO
git secrets --install
git secrets --register-aws   # 内置 AWS 凭据正则

# 添加自定义规则:拦截 GitHub 令牌格式
# ghp_ / gho_ / ghu_ / ghs_ / ghr_ 是 GitHub 各类 PAT 的前缀
git secrets --add 'gh[pousr]_[A-Za-z0-9_]{36,255}'

# 检查历史记录中是否已有泄露
git secrets --scan-history

4. 配置令牌自动过期与最小权限(GitHub Organization Settings)

在 GitHub 组织设置中强制以下策略:

# 这些设置在 GitHub Web UI 的 Organization Settings > Personal access tokens 中配置
# 以下为策略建议的 YAML 描述(非可执行配置,仅供对照检查)

organization_policies:
  fine_grained_pat:
    require_minimum_expiration: true        # 强制设置过期时间
    maximum_expiration_days: 90             # 最长 90 天
    require_repository_selection: true      # 不允许 "All repositories"
    allowed_permissions:
      contents: read                        # 默认只给读权限,写权限需单独审批
      issues: write                         # 按需开放
      pull_requests: write                  # 按需开放
    # 禁止的权限
    blocked_permissions:
      - administration                      # 禁止组织管理权限
      - delete_repo                         # 禁止删除仓库

  deploy_keys:
    require_read_only_default: true         # 默认只读,写权限需明确授权
    require_verified_domains: true          # 仅允许已验证域名的 key

5. 令牌泄露后的紧急轮换脚本

#!/bin/bash
# emergency_token_revoke.sh — 紧急注销可疑令牌并生成审计报告
# 使用前设置 ORG_NAME 和 SUSPECT_TOKEN_IDS

ORG_NAME="YOUR_ORG"
SUSPECT_TOKEN_IDS=("12345" "67890")  # 从审计日志中获取的可疑令牌 ID

echo "=== 紧急令牌注销 ===" > revoke_report.txt
echo "时间: $(date -u)" >> revoke_report.txt

for tid in "${SUSPECT_TOKEN_IDS[@]}"; do
  echo "注销令牌 $tid ..." | tee -a revoke_report.txt
  gh api orgs/$ORG_NAME/personal-access-tokens/$tid \
    -X DELETE \
    --input '{"reason": "security incident - suspected compromise"}' \
    2>&1 | tee -a revoke_report.txt
done

echo "=== 建议后续操作 ===" >> revoke_report.txt
echo "1. 检查该令牌访问过的仓库最近 commit 是否有异常修改" >> revoke_report.txt
echo "2. 通知令牌持有者重新生成令牌并使用最小权限" >> revoke_report.txt
echo "3. 在组织设置中启用令牌审批策略" >> revoke_report.txt
echo "4. 运行 git-secrets --scan-history 检查相关仓库" >> revoke_report.txt

cat revoke_report.txt

运行前需要将 YOUR_ORG 替换为你的 GitHub 组织名,SUSPECT_TOKEN_IDS 替换为从审计日志中确认的可疑令牌 ID。gh CLI 需要组织管理员权限。

供应链视角下的行动清单

Grafana 事件不只是"一家公司的令牌丢了",它提醒所有依赖开源组件的团队重新审视自己的上游信任链条。以下是建议的检查清单:

检查项 优先级 说明
令牌过期策略 P0 所有 GitHub 令牌设置最长 90 天过期,禁止无限期令牌
令牌权限范围 P0 禁止 "All repositories" 授权,按仓库逐个授权
CI/CD 凭据隔离 P1 CI 令牌与开发者令牌分离,CI 令牌仅限仓库读权限
硬编码扫描 P1 在 pre-commit hook 和 CI 中集成凭据扫描
发布签名校验 P1 对构建产物做签名发布,下游可验证完整性
令牌访问日志 P2 定期审计令牌使用记录,关注异常访问模式
事件响应预案 P2 预写令牌泄露的紧急轮换流程,明确谁有权注销令牌

Grafana 源码被拖走,但他们能在短时间内确认影响范围、定位泄露源头、完成凭证轮换并公开透明地披露——这套响应能力本身就是安全成熟度的体现。对大多数团队而言,事件本身未必能避免,但响应速度和加固深度决定了最终损失的大小。


相关推荐