BlackHatUSA2026:GitHub入侵检测

admin 2026-08-27 06:48:00 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档基于BlackHatUSA2026议题,提出利用GitHub自身事件流构建代码托管EDR以检测供应链攻击。核心观点是攻击者常利用平台允许的动作如移动Tag、修改Workflow进行投毒,传统SIEM仅关注单条事件不够。关键发现包括身份不一致、Tag状态差分和OIDC权限变化是稳定检测模式。可操作建议是建立状态快照、进行身份差分并监控Workflow权限变更。 综合评分: 85 文章分类: 供应链安全,安全运营,威胁情报,安全工具


Black Hat USA 2026:GitHub入侵检测

原创

Max Luo Max Luo

白帽子罗棋琛

2026年8月23日 08:19 广东

在小说阅读器读本章

去阅读

GitHub 正在告诉你仓库被入侵:用事件流构建代码托管 EDR

Black Hat USA 2026 议题笔记:GitHub Can Tell You’re Being Hacked. You’re Just Not Listening: Building EDR for GitHub from Its Own Event Stream

终端 EDR 的基本逻辑,是持续记录进程、文件、网络和身份行为,再从关联事件中识别攻击链。代码仓库同样有进程入口、身份切换、持久化、凭据使用和证据销毁,只是这些动作换成了 Commit、Tag、Workflow、OIDC、Release 和 Webhook。

不少团队已经把 GitHub Audit Log 接入 SIEM,却仍然很难发现供应链攻击。原因通常不是“没有日志”,而是只盯着单条高危事件。攻击者使用的动作往往都是平台允许的:推送 Commit、移动 Tag、修改 Workflow、触发发布、删除运行记录。风险来自对象之间不正常的关系。

公开课件把这套检测思路称为 GitHub EDR:监听平台活动,保存可比较的历史状态,用行为规则检测,再把相关信号拼成调查时间线。

图 1:用 GitHub 自身事件流构建 EDR 的议题课件封面

下面的表结构、查询和接收器代码,是根据课件架构整理的最小实现,不是项目仓库源码。

1、先把 GitHub 当作生产运行环境

对现代软件供应链来说,GitHub 不只是代码存储。它同时承担:

  • 身份和授权:用户、GitHub App、Deploy Key、GITHUB_TOKEN
  • 执行环境:GitHub Actions Runner;
  • 云身份交换:OIDC 联邦和 Trusted Publishing;
  • 制品发布:Release、Package、Action Tag;
  • 审批与变更:Pull Request、Branch Protection、Environment;
  • 取证记录:Workflow Run、Commit Graph、Webhook 和 Audit Log。

这意味着仓库安全不能只检查默认分支上的代码 diff。攻击者可以不改业务代码,通过移动 Release Tag、污染 Actions 缓存、覆盖 Workflow、伪造作者元数据或者让 OIDC 工作流在错误上下文中运行,完成同样的供应链投毒。

图 2:课件归纳的六类 GitHub 攻击技术

课件把常见手法分为六组:制品投毒、OIDC 滥用、Workflow 注入与触发器滥用、身份和 Commit 元数据伪造、证据销毁,以及通过 Fork 或游离对象注入恶意代码。

检测设计应围绕这些 TTP,而不是围绕某一次事件的 IOC。仓库名、攻击账号和恶意 Hash 会变,Tag 批量移动、作者与实际推送者不一致、发布工作流突然获得 id-token: write 等行为模式更稳定。

2、Git 元数据不是认证身份

Git Commit 中的 author.nameauthor.email、时间戳、Committer、Message、Tree 和 Parent 都可以由提交者设置。GitHub UI 可能根据邮箱把 Author 显示成一个真实账号,甚至带头像和可点击链接,但这不证明该账号完成了推送。

真正经过 GitHub 身份认证的,是事件中的 Pusher 或 Sender。

图 3:应比较可伪造的 Commit Author 与经过平台认证的 Pusher

数据入库时必须同时保留两类身份:

sql

CREATE TABLE github_commit_event (     delivery_id         TEXT PRIMARY KEY,     repo_full_name      TEXT NOT NULL,     commit_sha          TEXT NOT NULL,     author_name         TEXT,     author_email        TEXT,     author_login        TEXT,     committer_login     TEXT,     pusher_login        TEXT NOT NULL,     sender_login        TEXT NOT NULL,     ref                 TEXT NOT NULL,     received_at         TIMESTAMPTZ NOT NULL,     raw_payload_sha256  TEXT NOT NULL );  CREATE INDEX ON github_commit_event (repo_full_name, received_at); CREATE INDEX ON github_commit_event (author_email, received_at);

最基础的身份不一致检测可以直接写成 SQL:

sql

SELECT&nbsp; &nbsp; &nbsp;repo_full_name, &nbsp; &nbsp; commit_sha, &nbsp; &nbsp; author_login, &nbsp; &nbsp; pusher_login, &nbsp; &nbsp; received_at&nbsp;FROM&nbsp;github_commit_event&nbsp;WHERE&nbsp;author_login&nbsp;ISNOT NULLANDlower(author_login)&nbsp;<>lower(pusher_login) &nbsp;&nbsp;AND&nbsp;received_at&nbsp;>=&nbsp;now()&nbsp;-interval'24 hours';

这条规则本身只能作为低置信度信号。机器人代提交、Web UI 合并、自动同步和 Release Bot 都可能造成 Author 与 Pusher 不同。真正可疑的是它与其他条件同时出现:未签名 Commit、直接推送默认分支、修改 .github/workflows/、Pusher 首次出现在该仓库,或者相同伪造邮箱短时间横跨多个无关组织。

跨组织关联还需要调用 Commit Search API 或自建索引。课件给出的思路是:对每个 author != pusher 的事件,统计同一 Author Email 出现在多少个不相关 Owner 中。批量攻击工具复用伪造身份时,这个特征比单仓库告警更有价值。

3、Webhook 不是完整历史,Tag 投毒必须做状态差分

供应链攻击里一个常见动作,是把多个版本 Tag 同时移动到被污染的 Commit。依赖方如果写的是 uses: org/action@v1,下一次运行就会解析到新对象。

GitHub Activity 页面并不完整展示 Tag Push。课件引用的 Webhook 行为还指出:一次推送超过三个 Tag 时,不会为这些 Tag 创建事件。这意味着最需要检测的批量 Tag 移动,恰好可能绕过只依赖 Webhook 的采集器。

图 4:可靠检测需要通过 Tags API 保存并比较 Tag 指向

正确做法是周期性获取 git/refs/tags,建立不可变快照:

sql

CREATE TABLE&nbsp;github_tag_snapshot ( &nbsp; &nbsp; repo_full_name &nbsp;TEXT&nbsp;NOT NULL, &nbsp; &nbsp; tag_name &nbsp; &nbsp; &nbsp; &nbsp;TEXT&nbsp;NOT NULL, &nbsp; &nbsp; target_sha &nbsp; &nbsp; &nbsp;TEXT&nbsp;NOT NULL, &nbsp; &nbsp; observed_at &nbsp; &nbsp; TIMESTAMPTZ&nbsp;NOT NULL, &nbsp; &nbsp;&nbsp;PRIMARY KEY&nbsp;(repo_full_name, tag_name, observed_at) );

下面的查询检测一个采集周期内被移动的 Tag:

sql

WITH&nbsp;ordered&nbsp;AS&nbsp;( &nbsp; &nbsp;&nbsp;SELECT&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;repo_full_name, &nbsp; &nbsp; &nbsp; &nbsp; tag_name, &nbsp; &nbsp; &nbsp; &nbsp; target_sha, &nbsp; &nbsp; &nbsp; &nbsp; observed_at, &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;lag(target_sha)&nbsp;OVER&nbsp;( &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;PARTITIONBY&nbsp;repo_full_name, tag_name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ORDERBY&nbsp;observed_at &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp;AS&nbsp;previous_sha &nbsp; &nbsp;&nbsp;FROM&nbsp;github_tag_snapshot )&nbsp;SELECT&nbsp;repo_full_name, tag_name, previous_sha, target_sha, observed_at&nbsp;FROM&nbsp;ordered&nbsp;WHERE&nbsp;previous_sha&nbsp;ISNOT NULLAND&nbsp;previous_sha&nbsp;<>&nbsp;target_sha;

单个 Tag 移动可能是正常维护。应继续计算四个特征:同一时间窗内移动的 Tag 数量、是否集中指向同一 Commit、目标 Commit 是否可从受保护分支到达、移动后是否迅速回滚。最后一种“flip-flop”常用于投毒后恢复现场。

4、OIDC 让 Workflow 本身成为身份

过去攻击发布链路,常见目标是仓库 Secret 中的 NPM_TOKEN。使用 OIDC Trusted Publishing 后,长期 Secret 减少了,但攻击目标发生了变化:不再需要窃取 Token,只要让一个拥有 id-token: write 的 Workflow 在攻击者可控的上下文中运行。

图 5:需要检测新增或被修改的 OIDC 授权工作流

因此,检测对象要从 Secret Access 扩展到 Workflow 身份边界:

yaml

name:publishon:push:tags:&nbsp;["v*"]&nbsp;permissions:contents:readid-token:writejobs:release:environment:productionruns-on:ubuntu-latest

上面的配置并非天然不安全,但任何涉及以下变化的 Commit 都应提高风险分:

  • 新增 id-token: write
  • 触发器从受保护分支扩大到任意 Tag、Fork PR 或手工输入;
  • 删除 environment 审批;
  • 将 pull_request 改成 pull_request_target,同时 Checkout 不可信 PR 内容;
  • 发布 Job 使用可变第三方 Action Tag;
  • Workflow 由直接 Push 修改,而不是经过受保护 PR。

可以在入库的 Workflow 版本表上做差分:

sql

SELECT&nbsp; &nbsp; &nbsp;current.repo_full_name, &nbsp; &nbsp; current.path, &nbsp; &nbsp; current.commit_sha, &nbsp; &nbsp; current.pusher_login&nbsp;FROM&nbsp;workflow_version&nbsp;currentJOIN&nbsp;workflow_version previous &nbsp;&nbsp;ON&nbsp;previous.repo_full_name&nbsp;=&nbsp;current.repo_full_name &nbsp;AND&nbsp;previous.path&nbsp;=&nbsp;current.path &nbsp;AND&nbsp;previous.version&nbsp;=&nbsp;current.version&nbsp;-1WHERE&nbsp;current.has_oidc_write&nbsp;=trueAND&nbsp;previous.has_oidc_write&nbsp;=false;

云侧也要把 OIDC Subject、Repository、Ref、Workflow Path 和 Environment 写入访问日志。只在 GitHub 侧看到 Workflow 修改,却无法关联它最终换取了哪个云角色,调查链仍然是不完整的。

5、采集层必须同时有实时流、快照和对象检查

课件给出的 GitHub EDR 架构分成四步:Collect、Enrich、Detect、Act。Webhook Receiver、Snapshot Collector 和 CLI 负责采集,PostgreSQL 保存事件与上下文,SQL 查询运行行为规则,前端和 CLI 输出时间线。

图 6:采集、状态存储、检测与调查分层

三个数据源各自有明显缺口:

图 7:Webhook、GitHub API 和 Git 对象检查需要互相补位

Webhook 实时、字段丰富,但可以被禁用,接收失败后的事件也可能永久丢失。接收器首先要验证签名并对 Delivery ID 去重:

python

import&nbsp;hashlib&nbsp;import&nbsp;hmac &nbsp;&nbsp;defverify_webhook(secret:&nbsp;bytes, body:&nbsp;bytes, signature:&nbsp;str) ->&nbsp;bool: &nbsp; &nbsp;&nbsp;ifnot&nbsp;signature.startswith("sha256="): &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnFalse&nbsp; &nbsp; &nbsp;expected = hmac.new(secret, body, hashlib.sha256).hexdigest() &nbsp; &nbsp; supplied = signature.removeprefix("sha256=") &nbsp; &nbsp;&nbsp;return&nbsp;hmac.compare_digest(expected, supplied)

GitHub API 适合周期性快照和基线比较,不容易被攻击者关闭成“采集通道”,但 API 对象可能已经被删除,字段比 Webhook 少,还受 Rate Limit 约束。

Git Inspect 直接读取 Commit、Tree、Tag 和可达性关系,适合判断 Tag Provenance、游离 Commit 和不可能的 Parent 时间线。代价是需要 Clone 或对象抓取,不实时,成本也高。

因此,采集策略可以按层次安排:Webhook 负责低延迟信号;API 快照负责补洞和状态差分;只有命中候选规则的仓库再执行 Git Inspect。这样既保留证据,也避免对所有仓库持续全量 Clone。

Webhook 原始载荷不要只解析后丢弃。至少保存 Body Hash、Delivery ID、Event Type、接收时间和对象存储引用,以便在解析器升级后重新回放。

6、数据模型要能回答“之前是什么”

传统 SIEM 常把 GitHub 事件拍平成一行 JSON。这样可以搜索“谁删除了 Workflow Run”,却无法稳定回答:Tag 以前指向哪里、这个 Pusher 是否第一次修改发布 Workflow、当前 Release Asset 是否与昨天相同。

GitHub EDR 的核心不是告警规则数量,而是保存活动记忆。建议至少维护这些状态表:

text

repository_baseline &nbsp; &nbsp; &nbsp;默认分支、保护规则、Webhook、成员和 App tag_history &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Tag -> Commit 的时间序列 commit_identity &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Author、Committer、Pusher、签名和可达性 workflow_version &nbsp; &nbsp; &nbsp; &nbsp; YAML 摘要、触发器、权限、Action 依赖 workflow_run &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 触发 Ref、Actor、OIDC、Artifact、删除状态 release_history &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Tag、Asset 摘要、签名、创建者和修改时间 finding &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;规则、证据引用、风险分和处置状态

仓库快照还应带采集批次号。只有同一批次完整结束后才参与差分,避免 API 分页失败造成“对象被删除”的假阳性。

7、复合规则比单条高危规则更实用

课件展示的一个例子是:

  • 多个 Tag 同时指向同一 Commit,单独看是低危;
  • Tag 指向不在任何分支上的 Commit,单独看是中危;
  • 两者在同一仓库和时间窗内同时发生,升级为高危“批量 Tag 投毒到不可达 Commit”。

图 8:低、中置信度信号组合成高置信度攻击行为

SQL 可以直接表达这种相关性:

sql

SELECT&nbsp; &nbsp; &nbsp;burst.repo_full_name, &nbsp; &nbsp; burst.target_sha, &nbsp; &nbsp; burst.moved_tag_count, &nbsp; &nbsp; reach.is_reachable_from_default&nbsp;FROM&nbsp;tag_move_burst burst&nbsp;JOIN&nbsp;commit_reachability reach &nbsp;&nbsp;ON&nbsp;reach.repo_full_name&nbsp;=&nbsp;burst.repo_full_name &nbsp;AND&nbsp;reach.commit_sha&nbsp;=&nbsp;burst.target_sha&nbsp;WHERE&nbsp;burst.moved_tag_count&nbsp;>=4AND&nbsp;reach.is_reachable_from_default&nbsp;=falseAND&nbsp;burst.window_start&nbsp;>=&nbsp;now()&nbsp;-interval'15 minutes';

阈值不能从示例直接复制。大型 Monorepo、自动 Release 工具和版本回填任务可能正常移动多个 Tag。更稳妥的基线是每仓库统计过去 30 天的 Tag 变更分布,再对首次出现、超过历史高分位且同时具备不可达 Commit 的事件升级。

同样的复合思路还适用于:

  • Workflow 新增 OIDC 权限 + 直接 Push 默认分支 + 随后触发发布;
  • Author 与 Pusher 不同 + 相同伪造邮箱跨组织出现 + Commit 未签名;
  • 发布 Workflow 运行 + 日志随后删除 + Release Asset Hash 改变;
  • pull_request_target 读取 Fork 代码 + 使用可写 Token + 命中共享 Cache Key。

8、检测规则必须经过事件回放和噪声实验

课件没有只展示规则,还给出了一套验证过程:把已知事件重建成仿真场景,再用 GH Archive、GitHub API 和 Git Inspect 评估字段可用性、正常环境出现频率和已知事件召回率。

图 9:候选规则先经过事件仿真、数据补全和正常流量测量

每条规则至少需要四组固定样本:

yaml

rule_id:github.mass_tag_to_unreachable_commitpositive_fixtures:-mass_tag_force_move_to_dangling_commit-temporary_tag_swap_then_restorenegative_fixtures:-release_tool_creates_new_version_tags-repository_migration_rewrites_historyrequired_enrichment:-tag_snapshot-commit_reachabilitymetrics:-incident_recall-findings_per_1000_repositories-enrichment_api_cost

如果公共事件流缺少规则字段,应先通过 API 或 Git 对象补全,再测噪声;不要用缺字段的样本得出“零误报”。候选规则的结果不止保留“启用/禁用”,还可以选择降级、加 Allowlist、只做 Correlator,或者仅在高价值仓库启用。

9、Hardening 仍然优先于 Detection

检测不能替代平台加固。课件列出了 GitHub 在 2026 年前后增加或强化的多项机制,包括不可变 Release、Workflow 执行保护、actions/checkout 对特权 Workflow 获取 Fork PR 代码的限制,以及非协作者运行使用只读 Cache Token 等。

无论具体功能状态如何变化,企业侧都应先完成这些基础控制:

  1. Release 使用不可变资产和签名证明;
  2. 默认分支和 .github/workflows/ 目录要求 Code Owner 审批;
  3. OIDC Trust Policy 绑定 Org、Repo、Ref、Workflow 和 Environment;
  4. 第三方 Action 固定到完整 Commit SHA,而不是可移动 Tag;
  5. GitHub App 使用最小权限并定期复核安装范围;
  6. Webhook 配置、Branch Protection 和 Environment 进入配置漂移监控;
  7. 关键仓库保留外部事件副本,避免平台内证据被删除后无从恢复。

Detection 的作用,是发现控制被绕过、身份被合法滥用以及历史状态发生异常变化。不能把没有告警当成加固已经完成。

10、落地时先回答五个问题

准备建设 GitHub EDR 时,先确认:

  1. 是否同时采集 Webhook、API 快照和必要的 Git 对象,而不是只接 Audit Log;
  2. 是否保留历史状态,可以比较 Tag、Workflow、Release 和权限的前后变化;
  3. 是否区分 Git 元数据身份与 GitHub 认证身份;
  4. 是否能把 GitHub Workflow 关联到最终换取的云或制品仓库身份;
  5. 是否用仿真与正常数据共同验证规则,而不是看到 SQL 能返回结果就上线。

课件中的实现使用 PostgreSQL 保存统一视图,并通过 22 条规则和 12 条 Beta 规则覆盖多类供应链 TTP。这个数字本身不重要。更值得复用的是数据思路:实时事件负责告诉你“刚才发生了什么”,快照负责告诉你“对象变成了什么”,Git 图负责告诉你“这个对象从哪里来”。三者合起来,才具备接近 EDR 的调查能力。


资料来源

  • Itay Weinberger、Yair Mizrachi,Black Hat USA 2026:《GitHub Can Tell You’re Being Hacked. You’re Just Not Listening: Building EDR for GitHub from Its Own Event Stream》
  • Black Hat 官方 Session 页面

原始会议材料(仓库内)

  • 演讲课件 PDF

开源资料与原始议题 PDF

本文对应的 Markdown 原稿、Black Hat 原始议题 PDF 与配图已整理到 GitHub,可按文章编号查找和下载。

https://github.com/cybermaxluo/black-hat-usa-2026-talks

也可以点击文末“阅读原文”进入仓库。欢迎 Star、提交 Issue 或参与勘误。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:白帽子罗棋琛 Max Luo Max Luo《Black Hat USA 2026:GitHub入侵检测》

评论:0   参与:  0