Git Submodule漏洞(CVE-2018-17456)分析
国庆节的时候,Git爆了一个RCE的漏洞,放假回来进行应急,因为公开的相关资料比较少,挺头大的,搞了两天,RCE成功了
收集资料
一开始研究这个漏洞的时候,网上公开的资料非常少,最详细的也就github blog[1]的了。
得知发现该漏洞的作者是@joernchen, 去翻了下他的twitter,找到了一篇还算有用的推文:
另外在twitter搜索CVE-2018-17456
,得到一篇@_staaldraad验证成功的推文:
可惜打了马赛克,另外还通过Google也零零散散找到一些有用的信息(url都找不到了),比如该漏洞无法在Windows上复现成功,因为:
在Windows上不是有效的文件名。
研究分析
网上资料太少,只凭这点资料无法完成该漏洞的复现,所以只能自己通过源码、调试进行测试研究了。
使用woboq_codebrowser
生成了git v2.19.1
最新版的源码[2],方便审计。
通过源码发现在git
命令前使用GIT_TRACE=1
能开启git自带的命令跟踪,跟踪git的run_command
首先创建一个源,并创建其子模块(使用git v2.19.0进行测试):
1 | $ git --version |
从搜集到的资料看,可以知道,该漏洞的触发点是url参数,如果使用-
开始则会被解析成参数,所以尝试修改url
1 | $ cat .gitmodules |
从输出结果中,我们可以看到一句命令:
1 | git.c:415 trace: built-in: git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 -test /home/ubuntu/evilrepo/test1 |
我们设置的-test
被git clone
识别为-t
参数,漏洞点找到了,下面需要考虑的是,怎么利用git clone
参数执行命令?
继续研究,发现git有处理特殊字符,比如空格:
1 | $ cat .git/config |
如果有特殊字符,则会加上单引号
翻了下源码,找到了过滤的函数[3],是一个白名单过滤
只有大小写字母,数字和下面这几种特殊字符才不会加上单引号:
1 | static const char ok_punct[] = "+,-./:=@_^"; |
感觉这空格是绕不过了(反正我绕不动)
接下来继续研究如果利用参数进行命令执行
在翻twitter的过程中还翻到了之前一个Git RCE(CVE-2018-11235)[4]的文章,发现是利用hook来达到RCE的效果,在结合之前@_staaldraad验证成功的推文
可以很容易的想到一个方法,不过在讲这个方法前,先讲一些git submodule
的基础知识点吧
git submodule机制简单讲解
首先看看.gitmodules
的几个参数:
1 | [submodule "test1"] |
test1
表示的是submodule name,使用的参数是--name
,子项目.git
目录的数据会被储存到.git/modules/test1/
目录下
test2
表示的是子项目储存的路径,表示子项目的内容将会被储存到./test2/
目录下
test3
这个就很好理解,就是子项目的远程地址,如果是本地路径,就是拉去本地源
把本地项目push到远程,是无法把.git
目录push上去的,只能push .gitmodules
文件和test2
目录
那么远程怎么识别该目录为submodule呢?在本地添加submodule的时候,会在test2
目录下添加一个.git文件(在前面被我删除了,可以重新添加一个查看其内容)
1 | $ cat test2/.git |
指向的是该项目的.git
路径,该文件不会被push到远程,但是在push的时候,该文件会让git识别出该目录是submodule目录,该目录下的其他文件将不会被提交到远程,并且在远程为该文件创建一个链接,指向submodule地址:
(我个人体会,可以看成是Linux下的软连接)
这个软连接是非常重要的,如果远程test2目录没有该软连接,.gitmodules
文件中指向该路径的子项目在给clone到本地时(加了–recurse-submodules参数),该子项目将不会生效。
理解了submodule大致的工作机制后,就来说说RCE的思路
我们可以把url设置为如下:
1 | url = --template=./template |
这是一个模板选项,详细作用自己搜下吧
在设置了该选项的情况下,把子项目clone到本地时,子项目的.git
目录被放到.git/modules/test1
目录下,然后模板目录中,规定的几类文件也会被copy到.git/modules/test1
目录下。这几类文件其中就是hook
所以,只有我们设置一个./template/hook/post-checkout
,给post-checkout
添加可执行权限,把需要执行的命令写入其中,在子项目执行git chekcout
命令时,将会执行该脚本。
1 | $ mkdir -p fq/hook |
设置好了PoC,再试一次,发现还是报错失败,主要问题如下:
1 | git.c:415 trace: built-in: git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 --template=./fq /home/ubuntu/evilrepo/test2 |
来解析下该命令:
1 | git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/{name} {url} /home/ubuntu/evilrepo/{path} |
我们把{url}
设置为参数以后,/home/ubuntu/evilrepo/{path}
就变成源地址了,该地址被判断为本地源目录,所以会查找该目录下的.git
文件,但是之前说了,因为该目录被远程设置为软连接,所以clone到本地不会有其他文件,所以该目录是不可能存在.git
目录的,因此该命令执行失败
再来看看是什么命令调用的该命令:
1 | git.c:415 trace: built-in: git submodule--helper clone --path test2 --name test1 --url --template=./fq |
解析下该命令:
1 | git submodule--helper clone --path {path} --name {name} --url {url} |
path, name, url都是我们可控的,但是都存在过滤,过滤规则同上面说的url白名单过滤规则。
该命令函数 -> [5]
我考虑过很多,path或name设置成--url=xxxxx
都失败了,因为--path
和--name
参数之后没有其他数据了,所以--url=xxxx
都会被解析成name或path,这里就缺一个空格,但是如果存在空格,该数据则会被加上单引号,目前想不出bypass的方法
所以该命令的利用上毫无进展。。。。
所以关注点又回到了上一个git clone
命令上:
1 | git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/{name} {url} /home/ubuntu/evilrepo/{path} |
/home/ubuntu/evilrepo/.git/modules/{name}
路径是直接使用上面代码进行拼接,也找不到绕过的方法
最后就是/home/ubuntu/evilrepo/{path}
,如果git能把这个解析成远程地址就好了,所以想了个构造思路:/home/ubuntu/evilrepo/git@github.com:Hcamael/hello-world.git
但是失败了,还是被git解析成本地路径,看了下path的代码:
1 | if (!is_absolute_path(path)) { |
因为git@github.com:Hcamael/hello-world.git
被判断为非绝对路径,所以在前面加上了当前目录的路径,到这就陷入了死胡同了找不到任何解决办法
RCE
在不断的研究后发现,path=git@github.com:Hcamael/hello-world.git
在低版本的git中竟然执行成功了。
首先看图:
使用的是ubuntu 16.04,默认的git是2.7.4,然后查了下该版本git的源码,发现该版本中并没有下面这几行代码
1 | if (!is_absolute_path(path)) { |
所以构造的命令变成了:
1 | $ git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 --template=./fq git@github.com:Hcamael/hello-world.git |
之后把我执行成功的结果和@_staaldraad推文中的截图进行对比,发现几乎是一样的,所以猜测这个人复现的git环境也是使用低版本的git
总结
之后翻了下git的提交历史,发现2016年就已经添加了对path是否是绝对路径的判断。根据我的研究结果,CVE-2018-17456漏洞可以造成git选项参数注入,但是只有低版本的git才能根据该CVE造成RCE的效果。
UPDATE
github上有人公布了除了低版本的git都适用的PoC: https://github.com/joernchen/poc-submodule
再结合我的PoC,没有patch该漏洞的git都能被RCE
引用
Git Submodule漏洞(CVE-2018-17456)分析