RCE with Git submodule 分析-【CVE-2018-11235】
漏洞公告
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-11235
漏洞复现
Github上已经放出了Rogdham/CVE-2018-11235,原POC还需要git clone Spoon-Knife,对此我做了一些小修改。可以见CVE-2018-11235-DEMO
|
|
个人本地测试Git版本:
其中 build.sh 主要内容如下:
vuln.sh:
漏洞分析
在Git中存在Git Hooks的操作,它们被存放在一个repo的.git
目录下的hooks文件目录下:
当配置了这些hooks后,其本质上是脚本文件,会被Git所调用。以post-checkout
挂钩为例,如果在hooks中存在一个post-checkout
脚本,则当在该repo中执行git checkout
指令时,则会自动的去执行hooks目录下的post-checkout
脚本。
正常情况下,这些hook脚本并不会在clone期间进行传送。也就是说这些脚本是由客户端自己定制的。否则的话,服务端直接在repo中插入hook文件则直接造成了RCE。如下测试,clone的repo3中的hook目录中是没有post-checkout
脚本的:
而此次的RCE则是利用了Git的子模块功能,绕过了hook文件的限制。通过对子模块配置,将hook文件推送到了客户端中,从而造成RCE。
先介绍一下submodules即子模块。在一些项目中,项目本身需要包含并使用另外一个项目,而这两个项目又是相互独立的。为了保持提交的独立等,可以在Git中使用子模块submodules来解决。
以前面的build.sh中的内容为例:
这里我们创建了一个仓库repo_sub,接着通过
将repo_sub作为子模块添加到了仓库repo_par中,同时指定了路径vuln
(即别名)。
在Git文档:gitsubmodules中提到:
On the filesystem, a submodule usually (but not always - see FORMS below) consists of (i) a Git directory located under the $GIT_DIR/modules/ directory of its superproject, (ii) a working directory inside the superproject’s working directory, and a .git file at the root of the submodule’s working directory pointing to (i).
对于子模块而言,通常情况下,子模块的Git目录存放在$GIT_DIR/modules/
中,而其工作目录即父项目的工作目录,同时在工作目录下还有一个.git
文件来指向其Git目录。
当添加子模块完成后,在repo_par中会出现.gitmodules
文件,该配置文件保存了项目 URL 与已经拉取的本地目录之间的映射。
.gitmodules
文件同样受到版本控制的影响,会一起进行推送。这样clone的用户才知道去哪里拉取具体的子模块内容。它的文件格式可以见官方文档gitmodules:
这里对子模块vuln
而言,它的name
就是vuln
,path
就是vuln
,url即为./../repo_sub
。关于这个name,在官方文档中有这样一些表述:
The file contains one subsection per submodule, and the subsection value is the name of the submodule. The name is set to the path where the submodule has been added unless it was customized with the —name option of git submodule add. Each submodule section also contains the following required keys….
接下来以git version 2.17.0为例,根据Git源代码看看漏洞的触发点。当repo存在submodule时,会从.gitmodules
文件中读取相关信息,并将信息保存到cache中以节省资源。在submodule-config.c第563行gitmodules_cb
的最后将会调用parse_config
对.gitmodules
进行解析:
submodule-config.c第362行:
name_and_item_from_var(var, &name, &item)
用于从变量var
中获得name值,具体代码如下
假设.gitmodules
中内容为:
则通过parse_config_key解析出来的subsection会通过strbuf_add被添加到name中,即此时name的值为vuln
回到parse_config
中,此后将通过lookup_or_create_by_name(me->cache,me->gitmodules_sha1,name.buf)
获取子模块的信息并进行一系列操作。
在 submodule.c
第1617行,代码如下:
这通过xstrfmt("%s/modules/%s",get_git_common_dir(), sub->name)
来获得子模块的Git目录。在正常情况下,对于子模块 vuln 而言,get_git_common_dir
即父repo的Git目录,即.git
。sub->name
即前面获得的name值。拼接完成后子模块的Git目录即为.git/modules/vuln
但从前面的代码看来,对于name
和sub->name
,Git并没有做相关的输入检查/路径检查。如果我们通过设置name
为../../vuln
,则拼接后的路径即.git/modules/../../vuln
,即当前目录下的vuln
目录。这里存在一个目录穿越漏洞,之后的解析将把当前目录下的vuln
目录当做子模块的Git目录。
前面说到,.git/hooks
目录中的hook脚本并不会在clone期间进行传送。结合目录穿越漏洞,我们考虑这样的攻击方式:
- 将目录
.git/modules/vuln
拷贝到当前目录modules
下。 - 往
modules/vuln
目录中的hooks目录添加hook脚本 - 构造子模块,使其name成为
../../modules/vuln
,使子模块的Git目录信息指向当前目录下module/vuln
- 构造repo,使其在git clone时触发hook脚本
先考虑前2条,即对应build.sh中下述代码
第三条:
第四点,为了让.git/modules/../../modules/vuln
即modules/vuln
下的hooks目录中的hook脚本被调用,执行下述语句:
再添加一个子模块。当Git地进行git clone --recurse-submodules
时,会发现clone下来的目录中已经有了对应的子模块项目,因此实际上不需要clone,只要进行check out就行。而在check out时则会调用post-checkout脚本。
上述的分析针对 git version 2.17.0 进行。在一些低版本的Git中,由于功能等差异,可能上述环境会出错。Tony Torralba在其博客中复现了Git 2.7.4版本的漏洞,需要利用符号链接来进行RCE,具体的利用过程见 CVE-2018-11235 - Quick & Dirty PoC
补丁浅析
补丁见:https://github.com/git/git/commit/0383bbb9015898cbc79abd7b64316484d7713b44
主要是对从.gitmodules
中获取的name进行了检查
在name_and_item_from_var(var, &name, &item)
函数中调用了check_submodule_name
来进行检查:
Git在14年也爆过RCE洞(CVE-2014–9390),其原理也是利用了目录穿越加覆盖配置文件,在checkout时进行RCE。具体可见参考链接。
有些地方可能没解释清楚或有不当的地方,欢迎留言讨论。