本文详细介绍 Git 炸弹的工作原理及其危害,从 XML 炸弹的概念引入,解释 Git 炸弹如何利用 Git 对象存储机制创建深度嵌套的目录结构,导致内存耗尽和服务拒绝。文章分析了 Git 炸弹的制作方法、社区讨论过程和 Git 上游的修复措施,并提供了相关 CVE 漏洞信息,为安全研究人员和 Git 用户提供了安全防范的参考资料。
起源#
Git 炸弹因 XML 炸弹 (AKA “billion laughs”) 而得名,所以要理解 Git 炸弹,我们不妨先从 XML 炸弹说起。
下面是一段 XML 文件示例:
<?xml version="1.0"?><!DOCTYPE lolz [<!ENTITY lol"laugh out loud"><!ENTITY lol2"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"><!ENTITY lol3"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"><!ENTITY lol4"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"><!ENTITY lol5"&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"><!ENTITY lol6"&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"><!ENTITY lol7"&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"><!ENTITY lol8"&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"><!ENTITY lol9"&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">]><lolz>&lol9;</lolz>从 ENTITY 字段看起,每行 ENTITY 代表一个 XML 实体 (entity) 元素,一共 10 个实体元素,除了第一个实体 lol 定义了字符串 “laugh out loud”(AKA lol) 外,其余实体都是实体引用,它们每个都引用自上一个实体,且重复 10 次。
此 XML 文件的文档内容部分仅包含对实体 lol9 的一个实例的引用。但是,当它被 DOM 或 SAX 解析器解析时,遇到 lol9 时,它会扩展为 10 个 lol8,而每个 lol8 会扩展为 10 个 lol7,依此类推。到将所有内容扩展为文本 lol 时,字符串 “laugh out loud” 的数量已达 100,000,000。如果再增加一个类似结构的实体,或者第一个实体 lol 被定义为 10 个 “laugh out loud” 字符串的话,那么将有 10 亿 个 “laugh out loud”,即十亿大笑。
这段文本看似内容不多,占用内存不大,但是在解析过程中内容被指数级展开,会消耗大量内存资源,所以有人利用这个原理进行 DOS 攻击,使被攻击的机器的内存迅速耗尽,从而停止服务。
以上这种 XML 攻击,被称作 XML 炸弹。
Git 炸弹的原理大致也跟 XML 炸弹类似,它利用了 Git 的某种特性,使得重复的文本内容深度嵌套。所以接下来再来看一下 Git 炸弹的原理。
Git 炸弹原理#
我们都知道 Git 的基本数据结构有 commit, tag, tree, blob, blob 只存储文件内容,tree 存储文件名称,文件目录结构,commit 与 tag 类似于一种引用 (reference), 指向 tree。
每中数据都有自己的 hash ID, 所以对于 blob 来说,只要其中的内容是一样的,那么其 ID 就是一样的,不管其内容的文件名,文件路径是否相同。换句话说,Git 消除了 blob 的重复,允许不同的文件 (文件名,文件路径不同) 使用相同的 blob,目的是减少文件内容的重复。对于 tree 也是类似。
所以有人就利用这个特性制作了一个 Git 仓库,其结构类似:

之后,只要运行包含树的遍历操作的 Git 命令,如 git status, git checkout 等命令,Git 会先在内存中构造出该仓库的树结构,在这种特殊的仓库中,这个过程会消耗大量内存,因此只要这个仓库的树嵌套足够深,内存就会马上被消耗完,相关进程会被终止。
与 XML 炸弹类似,只要这种嵌套结构达到 10 层,或者底层的 blob 有 10 个,则整个过程展开会有 10 亿条 tree(路径)。
制作#
Git 炸弹第一次公开讨论是在 2017 年,Kate 在自己的博客 [2] 中讨论了制作 Git 炸弹的原理以及制作方法,制作程序是用 Python 写的,见:
如何防止#
经过 Github 以及漏洞平台 Hackerone 同意后,Kate 公开了自己在 Github 上自己的 git-bomb 仓库 [3],并且在自己的博客中公开讨论了 Git 炸弹的相关信息,之后马上引起了 Git 上游社区的关注,并且马上讨论了可能的修复方案:[4]
- Git 上游社区,最终补丁:[5]
Git 官方的修复是将遍历树的过程变得更快,使得对 Git 炸弹仓库做任何操作不至于等待很久。
其 commit message 写到:
You can see this in a pathological case where a commit addsa very large number of entries, and we limit based on abroad pathspec. E.g.:
perl -e ' chomp(my $blob = `git hash-object -w --stdin </dev/null`); for my $a (1..1000) { for my $b (1..1000) { print "100644 $blob\t$a/$b\n"; } } ' | git update-index --index-info git commit -qm add
git rev-list HEAD -- .
This case takes about 100ms now, but after this patch onlyneeds 6ms. That's not a huge improvement, but it's easy toget and it protects us against even more pathological cases(e.g., going from 1 million to 10 million files would taketen times as long with the current code, but not increase atall after this patch).这个修复并没有解决处理这种仓库导致内存消耗过大问题,只对处理过程的消耗时间做了一次优化。