談escapeshellarg繞過與參數注入漏洞

談escapeshellarg繞過與參數注入漏洞

來自專欄 長亭技術專欄

參數注入漏洞是指,在執行命令的時候,用戶控制了命令中的某個參數,並通過一些危險的參數功能,達成攻擊的目的。

0x01 從gitlist 0.6.0遠程命令執行漏洞說起

我們從gitlist說起,gitlist是一款使用PHP開發的圖形化git倉庫查看工具。在其0.6.0版本中,存在一處命令參數注入問題,可以導致遠程命令執行漏洞。

在用戶對倉庫中代碼進行搜索的時候,gitlist將調用git grep命令:

<?phppublic 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命令:

0x02 escapeshellarg為什麼沒有奏效?

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

  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的開發者用了另一種修復方案:

<?phppublic 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. Bashs 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有點影響正常搜索功能。

0x03 這不是PHP的專利

熟悉PHP語言的同學一定對PHP執行命令的方法感受深刻,PHP內置的命令執行函數(如shell_execsystem),都只接受一個「字元串」作為參數。而在內核中,這個字元串將被直接作為一條shell命令來調用,這種情況下就極為容易出現命令注入漏洞。

由於這個特點,PHP特別準備了兩個過濾函數:

  • escapeshellcmd
  • escapeshellarg

二者分工不同,前者為了防止用戶利用shell的一些技巧(如分號、反引號等),執行其他命令;後者是為了防止用戶的輸入逃逸出「參數值」的位置,變成一個「參數選項」。

但我在0x02中也已經說清楚了,如果開發者在拼接命令的時候,將$query直接給拼接在「參數選項」的位置上,那用escapeshellarg也就沒任何效果了。

Java、Python等語言,執行命令的方法相對來說更加優雅:

import subprocessquery = idr = subprocess.run([git, grep, -i, --line-number, query, master], cwd=/tmp/vulhub)

默認情況下,python的subprocess接受的是一個列表。我們可以將用戶輸入的query放在列表的一項,這樣也就避免了開發者手工轉義query的工作,也能從根本上防禦命令注入漏洞。但可惜的是,python幫開發者做的操作,也僅僅相當於是PHP中的escapeshellarg。我們可以試試令query等於--open-files-in-pager=id;

可見,仍然是存在參數注入漏洞的。原因還是0x02中說的原因,你把query放在了「參數選項」的位置上,無論怎麼過濾,或者換成其他語言,都不可能解決問題。

0x04 舉一反三

參數注入的例子還比較多,因為大部分的開發者都能理解命令注入的原理,但處理了命令注入後,往往都會忽略參數注入的問題。

最典型是案例是Wordpress PwnScriptum漏洞,PHP mail函數的第五個參數,允許直接注入參數,用戶通過注入-X參數,導致寫入任意文件,最終getshell。

另一個典型的例子是php-cgi CVE-2012-1823 ,在cgi模式中,用戶傳入的querystring將作為cgi的參數傳給php-cgi命令。而php-cgi命令可以用-d參數指定配置項,我們通過指定auto_prepend_file=php://input,最終導致任意代碼執行。

客戶端上也出現過類似的漏洞,比如Electron CVE-2018-1000006,我們通過注入參數--gpu-launcher=cmd.exe /c start calc,來讓electron內置的chromium執行任意命令。electron的最早給出的緩解措施也是在拼接點前面加上「--」。


推薦閱讀:

智能指紋鎖五年內能否像智能手機一樣成為大眾消費品?
首選同城幫,以舊換新迎新年
pycharm解析器找不到?初學者搞了一下午快瘋了
中國天眼竟成旅遊景區:別為了錢影響國家工程
自學Excel、PPT、word先學哪個?

TAG:科技 | 網路安全 |