Grafana Labs 近日在社交平台公开承认:有未经授权的第三方窃取了访问其 GitHub 环境的令牌,攻击者借此进入了 Grafana 的代码仓库。好消息是——经内部调查,没有客户数据或个人信息被触及,客户系统与运营也未受影响。Grafana 已定位到凭证泄露源头,注销了遭泄露的令牌并追加额外防护措施。更值得关注的态度是:Grafana 明确拒绝向黑客支付赎金。
一次令牌泄露,源码被拖走,公司选择不赎。这件事本身不算罕见,但把它放在供应链安全的语境下审视,值得每个依赖开源项目的团队认真对待。
令牌是怎么成为攻击入口的
GitHub 令牌(Personal Access Token、OAuth Token、Deploy Key 等)本质是一串凭据,拥有它就拥有对应权限范围内的仓库读写能力。Grafana 事件中,攻击者拿到的是一个能访问 GitHub 组织环境的令牌——这意味着不是某个个人仓库被攻破,而是组织级别的权限被滥用。
令牌泄露的常见路径包括:
- 硬编码进代码或配置文件,随后被推到公开仓库或泄露到外部;
- 存储在 CI/CD 环境变量中,但日志或调试输出意外暴露;
- 员工终端被入侵,本地
.git-credentials或环境变量被窃取; - 第三方集成过度授权,一个服务账号的令牌权限远超实际需要。
Grafana 表示已查明泄露源头,但未公开具体是哪条路径。无论细节如何,核心教训不变:令牌是高价值目标,一旦泄露,攻击者不需要漏洞利用、不需要零日,直接合法身份进入仓库。
源码被窃 ≠ 服务被攻破
Grafana 强调客户数据和个人信息未被访问,客户系统无影响。这背后有一个关键区分:源码泄露和服务被攻破是两件事。
源码被窃取的风险主要集中在:
- 攻击者可以研究代码寻找未公开漏洞,为后续攻击做准备;
- 供应链信任受损——下游用户会质疑"我用的版本是否被篡改";
- 内部工具、脚本、架构细节暴露,降低攻击者的信息收集成本;
- 如果源码含硬编码密钥或内部端点,则可能直接引发二次泄露。
但如果项目本身有健全的构建与发布验证机制(签名校验、可复现构建),源码泄露并不等于交付物被污染。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 源码被拖走,但他们能在短时间内确认影响范围、定位泄露源头、完成凭证轮换并公开透明地披露——这套响应能力本身就是安全成熟度的体现。对大多数团队而言,事件本身未必能避免,但响应速度和加固深度决定了最终损失的大小。