Skip to content

Git 炸弹

· 6 min
TL;DR

本文详细介绍 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 bomb

之后,只要运行包含树的遍历操作的 Git 命令,如 git status, git checkout 等命令,Git 会先在内存中构造出该仓库的树结构,在这种特殊的仓库中,这个过程会消耗大量内存,因此只要这个仓库的树嵌套足够深,内存就会马上被消耗完,相关进程会被终止。

与 XML 炸弹类似,只要这种嵌套结构达到 10 层,或者底层的 blob 有 10 个,则整个过程展开会有 10 亿条 tree(路径)。

制作#

Git 炸弹第一次公开讨论是在 2017 年,Kate 在自己的博客 [2] 中讨论了制作 Git 炸弹的原理以及制作方法,制作程序是用 Python 写的,见:

https://kate.io/blog/making-your-own-exploding-git-repos

如何防止#

经过 Github 以及漏洞平台 Hackerone 同意后,Kate 公开了自己在 Github 上自己的 git-bomb 仓库 [3],并且在自己的博客中公开讨论了 Git 炸弹的相关信息,之后马上引起了 Git 上游社区的关注,并且马上讨论了可能的修复方案:[4]

Git 官方的修复是将遍历树的过程变得更快,使得对 Git 炸弹仓库做任何操作不至于等待很久。 其 commit message 写到:

You can see this in a pathological case where a commit adds
a very large number of entries, and we limit based on a
broad 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 only
needs 6ms. That's not a huge improvement, but it's easy to
get and it protects us against even more pathological cases
(e.g., going from 1 million to 10 million files would take
ten times as long with the current code, but not increase at
all after this patch).

这个修复并没有解决处理这种仓库导致内存消耗过大问题,只对处理过程的消耗时间做了一次优化

相关 CVE#

参考链接#

  1. microsoft: xml-denial-of-service-attacks-and-defenses

  2. kate’s blog

  3. kate: original git-bomb repo on Github

  4. git upstream discussion

  5. git upstream fix