参数注入漏洞是指, 在执行命令的时候, 用户控制了命令中的某个参数, 并通过一些危险的参数功能, 达成攻击的目的.
0x01 从 gitlist 0.6.0 远程命令执行漏洞说起
我们从 gitlist 说起, gitlist 是一款使用 PHP 开发的图形化 git 仓库查看工具. 在其 0.6.0 版本中, 存在一处命令参数注入问题, 可以导致远程命令执行漏洞.
在用户对仓库中代码进行搜索的时候, gitlist 将调用 git grep 命令:
- <?php
- 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 命令:
0x02 escapeshellarg 为什么没有奏效?
导致这个漏洞的原因, 有几点:
开发者对于 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 的开发者用了另一种修复方案:
- <?php
- 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 有点影响正常搜索功能.
0x03 这不是 PHP 的专利
熟悉 PHP 语言的同学一定对 PHP 执行命令的方法感受深刻, PHP 内置的命令执行函数(如 shell_exec,system), 都只接受一个 "字符串" 作为参数. 而在内核中, 这个字符串将被直接作为一条 shell 命令来调用, 这种情况下就极为容易出现命令注入漏洞.
由于这个特点, PHP 特别准备了两个过滤函数:
- escapeshellcmd
- escapeshellarg
二者分工不同, 前者为了防止用户利用 shell 的一些技巧(如分号, 反引号等), 执行其他命令; 后者是为了防止用户的输入逃逸出 "参数值" 的位置, 变成一个 "参数选项".
但我在 0x02 中也已经说清楚了, 如果开发者在拼接命令的时候, 将 $query 直接给拼接在 "参数选项" 的位置上, 那用 escapeshellarg 也就没任何效果了.
Java,Python 等语言, 执行命令的方法相对来说更加优雅:
- import subprocess
- query = 'id'
r = 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 漏洞 https://github.com/vulhub/vulhub/blob/master/wordpress/pwnscriptum/README.md ,PHP mail 函数的第五个参数, 允许直接注入参数, 用户通过注入 - X 参数, 导致写入任意文件, 最终 getshell.
另一个典型的例子是 php-cgi CVE-2012-1823 https://github.com/vulhub/vulhub/tree/master/php/CVE-2012-1823 , 在 cgi 模式中, 用户传入的 querystring 将作为 cgi 的参数传给 php-cgi 命令. 而 php-cgi 命令可以用 - d 参数指定配置项, 我们通过指定
auto_prepend_file=php://input
, 最终导致任意代码执行.
客户端上也出现过类似的漏洞, 比如 Electron CVE-2018-1000006 https://github.com/vulhub/vulhub/tree/master/electron/CVE-2018-1000006 , 我们通过注入参数
--gpu-launcher=cmd.exe /c start calc
, 来让 electron 内置的 chromium 执行任意命令. electron 的最早给出的缓解措施也是在拼接点前面加上 "--".
来源: https://juejin.im/entry/5aeaea276fb9a07acf55ff12