一个分号差点攻破GitHub亿级代码仓库:少一行过滤代码,击穿多年内部信任体系

6分钟前

仅仅一个不起眼的分号,就能让任何拥有仓库推送权限的GitHub用户,在服务端执行任意指令。这次漏洞撕开的不只是一处输入校验的疏漏,更是多租户云平台延续多年的内部信任假设。


2026年3月4日,安全公司Wiz通过漏洞赏金计划向GitHub提交了一份漏洞报告,这个漏洞的攻击入口简单到出人意料:


一次构造过的git推送,只需要在推送选项的值中藏入一个分号。


GitHub内部仅用40分钟就复现了漏洞,从确认根因到完成云端修复上线总共只用了75分钟,全程不到两小时。


从理论上来说,任何对目标仓库拥有推送权限的已认证用户,都可以借助这个漏洞在处理推送请求的GitHub/GHES后端服务器上执行任意命令。


尽管GitHub官方博客明确声明「没有任何客户数据被访问、修改或泄露」,并且根据漏洞利用链的特征,该漏洞只会触发正常GitHub运营流程不会走到的异常代码路径,GitHub查询遥测数据后,确认除了Wiz的测试之外,没有发现该漏洞被实际利用的记录。Wiz也公开表示「我们没有访问任何其他租户的仓库内容」。


但这场风波,还是撬开了那个被业内信任了多年的内部信任假设。


一个分号击穿三层内部信任


想要理清这个漏洞的完整攻击链,需要先了解GitHub内部git推送流水线的运行逻辑:


当你执行git push推送代码后,请求进入GitHub内部的第一个节点是babeld——这是GitHub自研的git代理,负责把用户连接转发给下游服务。


babeld首先会向gitauth询问:请求推送的用户有没有对应权限,这次推送需要遵守哪些规则?gitauth会返回一份规则清单,包含文件大小上限、分支命名规则、钩子配置等信息。


babeld会把这份规则清单打包进名为X-Stat的内部请求头,转发给下游的gitrpcd服务。


gitrpcd是GitHub内部的RPC服务,它只认X-Stat这个请求头,并且完全信任请求头里的所有内容。


最后,负责推送前最终检查的pre-receive钩子拿到X-Stat,再决定这次推送是否允许通过。


整条流水线中,X-Stat头就是通行无阻的通行证。



GitHub内部git push流水线:babeld → gitauth → gitrpcd → pre-receive hook


这个X-Stat头的格式是一串用分号分隔的key=value键值对,比如a=1;b=2;c=3。解析时会把所有键值对依次存入字典,如果同一个key出现多次,后出现的值会默认覆盖掉之前的值。


问题到底出在哪?


git推送本身有一个正常功能叫push option,允许用户推送代码时附带传递自定义字符串给服务端。babeld会把这些自定义字符串原样打包进X-Stat头,格式为push_option_0=用户内容;push_option_1=用户内容。但babeld漏掉了最关键的一步:没有对用户传入的内容过滤分号。


X-Stat原本有一个控制文件大小限制开关的字段,名为large_blob_rejection_enabled,默认值为true,也就是开启限制。攻击者只需要构造一个push option,在值里先插入一个分号,再跟上large_blob_rejection_enabled=bool:false,babeld就会把这段内容原样写入X-Stat头,导致同一个key在头里出现了两次。



X-Stat字段注入示意:攻击者构造的push option覆盖合法字段的过程


解析时后写入的攻击者值覆盖了原本的合法值,文件大小限制就这么被轻松关闭了。整个攻击的起点,就是babeld少写的那一行过滤分号的代码。


其实这个漏洞背后的逻辑,在很多系统中都普遍存在:多个服务串联成一条流水线,每个节点只负责完成自己的任务,默认相信上游传递来的数据是干净安全的,全程没有任何节点做二次校验。


babeld没拦住恶意分号,gitrpcd收到数据不做验证,pre-receive钩子拿到字段直接使用,每一站都默认上游传来的内容可信,三层信任叠在一起,被一个分号全部击穿。


三步注入拿到服务器权限,GitHub公共版也被攻破


绕过文件大小限制,只是Wiz用来验证注入可行性的第一步测试,攻击的真正目标是拿到后端服务器的执行权限。


第一步攻击者先注入字段关闭沙箱:GHES(GitHub Enterprise Server,GitHub企业自建版)允许管理员自定义钩子脚本,在代码推送前自动运行,这些脚本默认在权限受限的沙箱中运行。但Wiz逆向分析后发现,沙箱是否启用由X-Stat头中的rails_env字段控制:值为production就进入沙箱,填其他任何值都会直接以git服务账户的身份运行脚本,没有任何隔离。而这个字段可以通过刚才的注入方法覆盖修改。


第二步注入custom_hooks_dir字段,把系统查找钩子脚本的根目录,从默认位置改成攻击者可以控制的路径。


第三步注入repo_pre_receive_hooks字段,填入路径穿越字符串,让系统跳出默认目录范围,去执行服务器上任意一个二进制文件。


三步串联完成后,GHES服务器返回了这样一行输出:remote: uid=500(git) gid=500(git) groups=500(git),这意味着攻击者已经成功以git服务账户的身份在GHES服务器上执行了代码。



Wiz研究员sagitz在X平台展示的概念验证:普通的git push命令后,远端服务器返回uid=500(git),证明代码已经以git服务账户身份在GitHub后端执行


Wiz一开始把同样的攻击链用在GitHub公共版GitHub.com上没有成功,推送完成后钩子没有触发,服务器也没有返回内容。继续逆向分析后才发现,X-Stat里还有一个标志位控制服务器是否以「企业模式」运行:GHES默认开启这个标志,因此自定义钩子可以正常运行,而GitHub.com默认关闭这个标志,自定义钩子根本不会被触发。但这个标志同样存在X-Stat头里,也可以通过注入覆盖。补上这第四步之后,整条攻击链彻底打通,Wiz在GitHub.com上成功执行了hostname命令,服务器返回了一个以.github.net结尾的内部主机名——攻击成功进入了GitHub公共版后端。


GitHub在事后博客中也承认,那条非生产环境的执行路径本来就不应该出现在GitHub.com的生产环境中:早期部署时曾经专门把这段代码排除在外,后来部署方式变更,排除代码的逻辑没有跟着迁移,这段废弃代码就悄悄留在了生产镜像里,一直没有人发现。漏洞利用能顺利打通,恰恰就是因为这段「不该存在」的代码偏偏留存在了生产环境中。


Wiz在测试中枚举了两台被攻陷的节点,每台节点上都能看到百万级其他用户和组织的仓库索引项,他们公开说明:

没有读取任何其他用户的仓库内容,只是用自己的测试账户确认了一件事:git账户确实拥有访问这台节点上所有仓库的权限。



GitHub的日志也印证了这一点:所有触发异常代码路径的记录全部来自Wiz的测试流量,没有发现其他账户的触发记录。能访问和实际窃取数据是完全不同的两件事,这次事件只停留在了可访问阶段,没有发生真实的数据泄露。但问题的根源还是出在多租户平台的底层设计上:GitHub.com把大量用户和组织的仓库放在同一批服务器上,统一交给同一个git服务账户管理——因为这个账户本来就需要处理所有用户的数据。只要攻击者拿到了这个账户的执行权限,该节点上的所有仓库都会对攻击者开放,共享架构带来了运营效率,也留下了这个无法彻底消除的结构性弱点。


云端两小时完成修复,自建实例多数仍未补丁


这次漏洞最难收尾的其实是用户自建的GHES实例:在Wiz公开披露漏洞时,统计显示有88%的GHES实例还没有安装修复补丁。


按照GitHub官方的修复建议和NVD的记录,管理员需要升级到对应受支持分支的最新补丁版本,目前公开的修复版本包括3.14.25、3.15.20、3.16.16、3.17.13、3.18.7、3.19.4,以及3.20.0或更高版本。GHES管理员需要做两件事:立刻升级版本,然后查看/var/log/github-audit.log日志,搜索推送选项中包含分号的记录,核查是否存在未授权的注入攻击痕迹。


这个漏洞的触发门槛并不高,只需要已认证用户对实例上任意仓库拥有推送权限即可:攻击者只要拿到企业内部一个普通员工的账号,哪怕这个账号只能推送一个不起眼的内部项目,就能直接拿到整个GHES实例的服务器执行权限。而GHES通常存储着企业全部的代码资产、CI配置、内部凭证,一旦被攻破后果非常严重。并且这个漏洞也不是GHES管理员当前需要修复的唯一高危漏洞。



GitHub Enterprise Server 3.19.5版本更新说明,单个版本同期修复了四个高危漏洞,链接:https://docs.github.com/en/enterprise-server@3.19/admin/release-notes


仅看3.19.5这一个版本的更新说明,同期修复的高危漏洞就还有四个:CVE-2026-5845、CVE-2026-5921、CVE-2026-4821、CVE-2026-4296。使用SaaS托管代码,补丁打好之后会自动生效,而自建GHES需要管理员手动跟进每一个补丁升级,很多企业的升级进度都远远滞后。


AI降低逆向成本,闭源软件的「安全护城河」正在消失


这次漏洞披露还有一个很容易被忽略的细节:Wiz在技术博客中专门解释了这次能找到漏洞的原因。GitHub内部的git流水线都是编译后的闭源二进制文件,之前不是没人想过审计,但是人工逆向的成本太高,往往做到一半就放弃了。这次重新开展审计,是因为逆向工具已经变了。


过去很多闭源软件所谓的「相对安全」,并不是代码本身足够严密,而是依赖逆向分析成本太高这个门槛,没人愿意花大代价来审计挖掘漏洞。代码不公开不代表不能被审计,只是过去审计闭源软件在经济上不划算,现在这笔账的算法已经彻底变了。


过去资深逆向工程师需要投入几个月才能完成的工作,用AI工具组合只需要几周就能完成一轮审计。Wiz也公开说明,这次是他们公开记录中,第一批依靠AI在闭源二进制中找到关键级漏洞的案例,使用的组合是IDA Pro加MCP协议加大语言模型。



Wiz Research博客认为:AI增强的逆向工程工具,将会在发现需要深入跨组件分析的漏洞类型方面发挥越来越重要的作用,链接:https://www.wiz.io/blog/github-rce-vulnerability-cve-2026-3854


现在AI不只是在帮助开发者写代码,也在帮助安全研究者拆代码找漏洞。所有长期靠「闭源没人审」维持安全状态的系统,都应该重新评估自己真实的暴露风险了。


被分号撬开的信任假设,留下三个值得深思的问题


回到这次漏洞事件本身,CVE-2026-3854披露后,GitHub的应急响应可以说是教科书级别,补丁及时到位,也没有发现已知的真实攻击利用。但这件事还是留下了三个值得后端开发者认真思考的结论:


第一,系统设计层面的隐患比单点漏洞更难察觉。分隔符协议、跨服务隐式信任、后写入覆盖先写入的解析语义,这三个设计单独存在都不是大问题,组合在一起就是定时炸弹。任何使用分号、竖线或换行传递内部元数据的系统,开发者今天就应该去检查一遍代码。


第二,多租户平台的风险是结构性的,这不只是GitHub一家的问题。只要不同用户的数据存放在同一台机器上,由同一个服务账户管理,这个风险点就一直存在。攻击者一旦拿到执行权限,系统隔离能起到多少防护作用,完全取决于共享账户的权限边界设计得够不够严谨。


第三,AI辅助逆向已经重新改写了攻防的成本平衡,AI辅助逆向正在改变攻防双方的成本结构,下一批被挖出大量关键漏洞的,大概率就是那些长期靠「闭源没人审」支撑的企业级软件。


这次GitHub没有发生真实的数据泄露,所以更值得我们认真讨论这件事:那个被信任了多年的X-Stat内部头,在一个小小的分号面前,没有起到任何防护作用,原本牢不可破的内部信任假设,就这样被轻易击穿。


参考资料:


https://www.wiz.io/blog/github-rce-vulnerability-cve-2026-3854


https://x.com/sagitz_/status/2049153195243372569?s=20


本文来自微信公众号“新智元”,作者:新智元,编辑:元宇,36氪经授权发布。

本文仅代表作者观点,版权归原创者所有,如需转载请在文中注明来源及作者名字。

免责声明:本文系转载编辑文章,仅作分享之用。如分享内容、图片侵犯到您的版权或非授权发布,请及时与我们联系进行审核处理或删除,您可以发送材料至邮箱:service@tojoy.com