New Research: A "Verified" GitHub Commit Is NOT Unique
摘要
Git 提交通过内容哈希作为唯一标识,但 Carnegie Mellon 大学 Jacob Ginesin 预印本论证签名可变性允许攻击者不使用私钥,生成与原提交树、元数据、消息、签名相同但哈希不同的新提交,并获得 GitHub “Verified” 徽章。该机制通过 ECDSA 签名对称反转、OpenPGP 未覆盖子包插入、S/MIME 非标准 BER 编码实现,GitHub 服务器不规范签名容器时接受这些,保留各自 “Verified” 记录(即使密钥撤销仍有效)。严格本地 git verify-commit 对 OpenPGP 接受而 S/MIME 拒绝,原文不涉及哈希碰撞或 SHA-1/256 迁移。
荐读理由
GitHub 服务器端不 canonicalize 签名容器,导致相同签名的不同字节提交保留独立 Verified 记录,依赖它的供应链系统必须处理这种 hash-chain malleability 以免意外撤销
原文
Git identifies every object by a hash of its contents, and for a commit that hash covers the tree, the metadata, the message, and the raw bytes of any signature. A large amount of supply-chain machinery treats that hash as a unique, immutable name for a specific piece of signed content. Continuous-integration pipelines pin to it, dependency managers lock to it, and incident responders block or revert by it.
A new preprint by Jacob Ginesin of Carnegie Mellon University argues that this assumption breaks. According to the paper, given any signed commit, an attacker who does not have the signing key can produce a second, distinct commit with an identical tree, identical metadata, a valid signature, and a "Verified" badge from a forge such as GitHub. Only the commit hash changes. Because each commit names its parent by hash, the change cascades down the chain, which the author calls "hash chain malleability."
The mechanism is signature malleability, not a broken hash function. The paper is explicit that it does not exhibit two different contents sharing one hash, so the finding is unrelated to the collision resistance of SHA-1 or the SHA-256 migration Git is moving toward. Instead, one logical commit admits many byte-different but equally valid signed serializations, each hashing to a different value.
The paper describes three routes, together covering every GPG-based scheme GitHub verifies. For ECDSA, it uses the classic algebraic symmetry that turns a signature (r, s) into an equally valid (r, n-s). For RSA and EdDSA, which resist that trick, it appends a well-formed but ignored subpacket to the unhashed region of the OpenPGP signature packet, a region RFC 4880 says is not covered by the signature. For S/MIME, it re-encodes a length field inside the CMS structure into a non-canonical form that is invalid strict DER but still accepted as BER.
What turns these from local curiosities into a supply-chain concern, the paper says, is GitHub's server-side behavior. According to the research, GitHub does not canonicalize the signature container before verifying it, so it accepts the ECDSA inversion, the OpenPGP subpacket insertion, and the non-canonical S/MIME encoding, and issues each resulting commit its own durable "Verified" record keyed on the commit hash. GitHub's own documentation confirms the underlying feature: a commit retains its verified status based on the record created at verification time, even if the signing key is later revoked or expires. The paper notes that strict local verification is more mixed. It says git verify-commit accepts the two OpenPGP routes but rejects the non-canonical S/MIME encoding, which GitHub accepts anyway.
这条对你有帮助吗?