周三下午三点,一个安全研究员在GitHub上推了一个测试仓库,向所有人演示了一个令人不安的事实——那个绿色的“Verified”小徽章,可能并不像我们想象中那么可靠。事情的起点很简单:一个已经签过名的Git提交,在任何人手里都能被“锻造”出一份全新的副本,文件内容、作者、日期完全一致,数字签名仍然通过验证,GitHub也照样盖上“Verified”的批准章。唯独那个本该独一无二的提交哈希值,已经变了。
这一点之所以令人心惊,是因为整个软件供应链的很多环节,都把经过验证的提交哈希当作一份内容的永久身份证。如果你封锁了一个恶意提交的哈希值,攻击者可以用同一套内容,产出另一枚从未被系统列入黑名单、却同样打着“已验证”标签的哈希值,重新推送进来。那些依赖哈希做去重、溯源日志和可重现构建记录的体系,同样继承了这一软肋。甚至一个被攻破或存在恶意的镜像,也能向克隆仓库的人提供签名有效、但哈希值与官方仓库不同的提交。
![]()
不过这里需要先划清一条边界:这并不是一种可以绕过签名检查、偷偷替换代码的攻击。所有副本里的文件都是一模一样的,所以你通过一个固定哈希值取回的内容,要么恰恰是你期望的那一份代码,要么就会干脆失败。没有漏洞编号CVE,没有厂商公告,你也不必在自己的仓库里改动任何东西——因为问题的根源在于代码托管平台如何定义“已验证”,修补的责任在平台那一边。
这项研究来自卡内基梅隆大学的博士生、同时也是密码学审计公司Cure53成员的Jacob Ginesin。他在七月二日上传到arXiv的一篇五页论文里,不仅公开了一个能够实现全部三种攻击方式的工具,还给出了两个演示仓库,那些被“锻造”后的提交至今仍在GitHub上显示着显眼的“Verified”。
随着链条向上蔓延,事情会变得更麻烦。由于每个提交都通过哈希值来指认自己的父提交,一旦链条中的某一环被“锻造”出新的哈希,所有在这上面的后续提交也会被迫被重新分配哈希值。Ginesin提供的工具会把整条链一起重写,以保持一致性。但这里有一个连锁反应:如果一个提交本身带有签名,那么它只要发现自己指向的父提交哈希发生变化,它的绿色“已验证”标志就会自动消失。Ginesin把这种现象命名为“哈希链可锻造性”。
而这一切的根本原因,在于数字签名本身的可锻造性。一个提交的哈希值是在计算它内部全部内容后得出的,其中就包括签名头部里的原始字节。很多数字签名都可以在不改变文件代码的情况下,被改写成另一种形式却依然有效。一旦这些字节被改写,整个提交的哈希值也会跟着变化,哪怕连一行代码都没有动过。
研究团队一共给出三种攻击路径,覆盖了GitHub所验证的全部GPG方案,外加S/MIME。对ECDSA密钥来说,攻击手法用到了一种经典的椭圆曲线代数技巧,把签名中的数值s翻转成n减s,两种形式都有效。这种新签名不仅能通过本地的git verify-commit校验,到了GitHub上也能顺利拿到“Verified”徽章。对于RSA和EdDSA密钥,攻击方式则是往签名的“未哈希”区——也就是签名刻意不覆盖的那部分数据——中塞进一个额外的、会被忽略的字段。签名校验照旧通过,但签名字节变了,提交的哈希值也随之改变,本地和GitHub两边都会接受。而对于S/MIME也就是X.509证书,攻击者会重新编码签名DER结构中的一个长度字段,把它写成更长但非标准的形态。有趣的是,如果严格地用gpgsm在本地做校验,这个签名是会被拒掉的,但GitHub仍然会标记为“已验证”,这一现象在Ginesin的演示工具里得到了完美复现。
这三种路径的共同点,其实都指向同一个事实:GitHub并没有在计算提交哈希值之前,先对签名做规范化处理。签名内部一些在数学上等价、或是被解析器忽略的字节差异,最终被原样带入哈希计算,让同一个签名的不同表达形式拥有了截然不同的哈希。这或许不是密码学的灾难,却是供应链信任模型的一块松动砖——我们在靠一个会变脸的标志来传递“安全”的信号。
当“已验证”不再意味着那条代码一定来自我以为的那个人和那个仓库,而只是“在一个未被劫持的签名流程里出示了一份看起来合规的签名”时,所有基于提交哈希构建的锁定、审计和分发逻辑,就都需要再重新打量一次那个绿色的勾号了。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.