2018 年 6 月 28 日 UTC 20:19,攻击者成功进入一名管理员的 GitHub 账户。20:25,攻击者创建了另一名管理员,随后开始把 Gentoo 开发者逐出他们自己的组织。到 20:38,一条破坏性命令已经出现在公开的 gentoo/gentoo 镜像中。21:28,GitHub 冻结了该组织。[1]
这次接管确实发生了,GitHub 上可见的仓库已经失去可信度,部分贡献基础设施随后约五天无法使用。[1][2] Gentoo 位于项目自有基础设施上的权威软件包仓库依旧完整。项目把最终权威保留在自有系统中,最受欢迎的公开副本只承担镜像角色,因此响应人员手中的材料比备份更有价值:一份独立记录,明确各分支原本应当包含什么。[1][2] 同期的独立 Linux 媒体报道也划出了相同界线:受侵的是 GitHub 副本,Gentoo 的 master ebuild 仓库未受影响。[3]
正是这条架构分界,让事件值得细读。“镜像只负责分发,权威仓库另有所在”限制了影响所能扩散的范围,却无法消解入侵造成的损失。事件同时揭示,Git 对象数据库之外还存放着大量重要状态,包括管理员权限、pull request、CI、仓库设置与贡献者信任。
图片背景:这张 FOSDEM 照片摄于 2005 年 2 月,距入侵事件尚有多年。Patrick Lauer 身上的 Gentoo T 恤标示出项目在面对面贡献者文化中的位置;公共代码托管平台后来把这种文化延伸到了线上。画面只呈现社区人物,攻击者与 2018 年响应现场均在画面之外。[8]
从密码被猜中到遏制事态:70 分钟
Gentoo 的事后报告把根本原因追溯到一名组织管理员使用的密码。收集到的证据显示,一处网站泄露的信息,让无关网站上的密码也变得容易猜中。这次登录一成功,攻击者便取得组织所有者权限,可以邀请新账户、授予管理员权限、移除合法成员、更改账单邮箱并改写仓库。[1]
这一连串动作十分显眼。成功登录六分钟后,一个虚设的管理员账户被加入组织,正常成员也开始遭到移除。成员移除邮件提醒了开发者;20:29,首次有人察觉异常,基础设施团队于 20:36 收到报告,第一份提交给 GitHub 的滥用举报于 20:49 发出。GitHub 在 21:28 冻结组织,从账户被攻入到平台完成遏制,间隔约 69 分钟。[1]
Gentoo 后来承认,快速发现事件也有运气成分。若入侵者更加隐蔽,组织成员关系可以保持原状,内容改动也可以避开显眼位置。这名攻击者选择强制推送改写后的历史、篡改 README,并把删除命令放进与构建相关的文件。同一批强制推送既破坏了受信任的分支顶端,也降低了已有 checkout 通过普通 pull 静默接受新历史的概率。[1]
因此,评估事件速度时,需要把响应端与攻击端放在一起看。响应流程确实很快:开发者察觉异常,通过正式渠道与私人联系逐级上报,并借助审计日志锁定受侵账户。攻击者的行为同样显眼。组织应巩固前一种优势,并在设计响应时假定对手会保持隐蔽。
三个仓库均遭投毒,方式各有不同
Gentoo 确认了三个仓库中恶意内容存在的时间窗口。在 6 月 28 日 20:38 至 6 月 29 日 06:58 之间新克隆 gentoo/gentoo,所得副本存在目录树遭篡改的风险。gentoo/musl 的相应窗口为 20:56–06:59,gentoo/systemd 则为 21:07–06:57。Gentoo 建议在这些时段克隆过仓库的用户丢弃副本,改从修复后的来源重新开始。[1]
恶意载荷很粗糙,分发界线仍然重要。攻击者把不同形式的 rm -rf 插入 ebuild 或配置内容。Gentoo 判断,技术防护使这些命令在最终用户环境中执行的概率很低;报告也未称公开平台上的入侵已经波及项目托管的仓库。[1] 这一判断的范围远窄于“什么也没发生”。新克隆中依然存在攻击者控制内容的风险,在与独立权威记录完成比较之前,响应人员仍需把 GitHub 上每个分支顶端一律按不可信状态处置。
这些仓库的来源也不相同。Gentoo 的主要仓库位于自有基础设施上,再镜像到 GitHub,方便贡献者在惯用平台参与项目。事件报告特别指出,gentoo/systemd 是一个例外:它直接存放在 GitHub 上,没有从 Gentoo 镜像而来。这项例外让恢复更加复杂,因为“用确认完好的权威分支覆盖镜像”无法完整处理每一个仓库。[1]
这里出现了第一条长期有效的教训。镜像、上游与权威仓库等标签,其含义应由强制执行的数据流来确定,单靠团队内部口耳相传无法保证准确。对于每个仓库,运维人员都要清楚:哪个系统有权推进权威 ref,哪些系统只负责复制;当代码托管平台的现状本身成为取证材料时,又该去哪里找到一份独立且确认完好的分支顶端。
源代码安然无恙,协作层面却遭到破坏
Gentoo 的 GitHub 组织不可用期间,Proxy Maintainers 工作流之外的开发仍在继续,事后报告记录了事件期间的 700 余次提交。这是权威仓库与镜像分离的设计确实发挥作用的有力证据。[1] 这项证据不能用来推断 GitHub 可以舍弃。
Pull request CI 中断了。许多外部贡献者依靠 GitHub pull request,通过 Proxy Maintainers Project 参与项目。既有 pull request 与原始 commit 的关联断开,随后遭到关闭;GitHub 无法重新建立关联,贡献者只得重新创建 pull request。代码对象可以恢复,代码对象之外的关联——评审对话、署名流程、CI 状态以及待处理贡献队列——却不会在干净分支强制推送后自行重现。[1]
Gentoo 还发现,项目没有备份 GitHub 组织配置。其行动清单包括:离线保存组织设置、在外部采集审计事件、减少所有者人数、强化成员退出流程、强制双因素认证,并为仅存在于 GitHub 的仓库建立镜像。[1] 由此,事后报告把“仓库恢复”扩展成了控制平面恢复。
Mozilla 发布的仓库安全指南划出了相近界线。指南建议强制启用双因素认证、限制生产分支访问、把分支保护规则应用到管理员、为生产 commit 与重要 tag 签名,并审计组成每次发布的 commit。指南还要求定期复查组织与仓库设置,把代码托管平台配置当作需要检查的状态,避免只凭约定推定其正确性。[4]
Git hash 证明内容,授权另需证据
Git 的内容寻址让比较变得精确。Gentoo 的事后报告记录了已知正常与恶意的 commit ID,随后在 6 月 29 日 06:57 至 06:59 之间,把三个受影响仓库恢复到确认完好的分支顶端。[1] 响应人员因此可以准确说明,一个分支在修复前后分别指向哪棵目录树。
commit hash 对授权问题没有答案。受侵管理员若把 main 移到一个新 hash,Git 可以验证下载的对象与该 hash 相符,却无法判断项目是否有意批准这次移动。前者回答“这些字节在内部是否一致?”,事件响应还要回答另一项问题:“我们认可的权威主体是否批准了这个 ref?”消费者若使用从受侵代码托管平台之外取得的密钥验证签名,带签名的发布 tag 就能补上这项声明;git verify-tag 命令的用途正是验证此类签名。[7]
签名本身还称不上完整的事件响应方案。签名密钥同样会遭窃,项目若对过多例行事件签名,验证就会失去辨识力,贡献者也会忽略失败的检查。签名的价值来自两种权威凭据各自独立:代码托管平台的登录权限决定谁能操作平台,另一把独立签名密钥则为发布或 ref 作证。若两项凭据都放在同一浏览器会话或同一自动化 secret store 中,这种分离只剩形式。
GitHub 当前的控制措施可以收窄平台一侧的风险。组织可以要求成员、外部协作者与账单管理员启用双因素认证。[5] 受保护分支可以阻止强制推送、限制推送者、要求评审或签名 commit,并把规则应用于管理员;文档同时指出,管理员与具有绕过权限的角色默认享有豁免,项目需要显式封堵这条通道。[6] 如果受侵角色能够静默绕过一项控制,其实际强度便低于设置页标签给人的印象。
恢复需要两项彼此独立的证明
仓库层面的证明来自比较:把 Gentoo 托管的已知正常 ref 与遭到篡改的 GitHub ref 对照,再按受控流程恢复。账户层面的证明来自审计:找出入侵入口账户,撤销它在 Gentoo 系统中的权限,轮换凭据、检查日志,并让被盗密码彻底失效。到 6 月 28 日 23:40,Gentoo 已锁定入口并撤销该账户对项目核心资产的访问权限。修复后的仓库在次日上午完成推送,GitHub 直到 7 月 3 日 11:46 才重新公开该组织。[1]
这段时间差说明,仅有“一份干净的 clone”还达不到结案标准。响应人员还要查清组织成员、恶意账户、被改动的邮箱设置、pull request 损失,以及受侵用户在 GitHub 之外的活动。代码恢复回答了哪些内容应当对外发布;控制平面审查回答了谁还能再次改动这些内容。
对于把代码托管平台作为唯一权威主机的小型项目,事故期间无法照搬 Gentoo 的设计,因为镜像背后没有第二个权威服务等待接管。实际替代方案是一份平台外恢复记录:频繁更新的只读 clone、受保护且经外部验证的发布 tag、导出的组织配置、存放在代码托管平台之外的审计流,以及至少两名能够取得联系并使用抗钓鱼认证的所有者。这些措施没有哪一项能单独把副本变成权威来源;合在一起,它们能够保存日后有序重建权威所需的证据。
对于拥有自建 Git 服务的大型项目,只把 GitHub 称作镜像仍然不够。测试复制方向,盘点只存在于 GitHub 的例外仓库,确认受侵镜像没有回写权威 ref 的能力。还要演练相关命令与沟通流程,以便冻结贡献入口、通过独立渠道发布确认完好的 commit ID、重建镜像、重新签发 CI 凭据,并在成员与设置完成审计后再重新开放。
Gentoo 能把危险代码限制在一定范围内,是因为它清楚权威记录位于何处。更棘手的损失出现在 Git 对象从未记录的部分:身份、权限、评审与贡献者工作流。“全部自行托管”与“镜像能让入侵安全无害”都是从事件中抽出的过度结论。这份事后报告真正留下的教训,是把负责便捷分发副本的系统,与证明项目实际授权了哪份副本的证据分开。
来源
- Gentoo Infrastructure,“2018-06-28 Github”事件报告——根本原因、仓库风险窗口、影响、教训、行动项、commit ID 与 UTC 响应时间线。
- Gentoo Linux,“Github Gentoo organization hacked — resolved”,2018 年 6 月 28 日至 7 月 4 日——事件同期的用户指引,并澄清 Gentoo 托管的开发基础设施未受影响。
- Michael Larabel,“Gentoo Was Compromised On GitHub”,Phoronix,2018 年 6 月 28 日——关于 GitHub 入侵与 master ebuild 仓库未受影响的同期独立报道。
- Mozilla Firefox Operations Security,“GitHub/Repository Security”——敏感仓库指南,涵盖成员管理、2FA、分支保护、签名、发布审计与定期配置复查。
- GitHub Docs,“Requiring two-factor authentication in your organization”——当前组织级 2FA 的覆盖范围与操作前提。
- GitHub Docs,“About protected branches”——当前针对强制推送、删除、签名 commit、评审、推送者限制与管理员绕过的控制措施。
- Git Project,“git-verify-tag”文档——用于验证 Git tag 对象密码学签名的命令约定。
- GerardM,“Wikimedia and Gentoo”,Wikimedia Commons——Gentoo 开发者 Patrick Lauer 与 Jimmy Wales 在 FOSDEM 2005 的真实照片,本文以此作为题图。