escapeshellarg绕过与参数注入漏洞

参数注入漏洞

在执行命令的时候,用户控制了命令中的某个参数,并通过一些危险的参数功能,达成攻击的目的。

举例

gitlist 0.6.0远程命令执行漏洞

在用户对仓库中代码进行搜索的时候,gitlist将调用git grep命令

1
2
3
4
5
6
7
8
9
10
11
12
<?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命令

原因:

虽然–open-files-in-pager=id;被escapeshellarg处理过两边加上了单引号,但是它的位置在参数选项(option)的位置上,如果想要安全,不被命令执行,就应该被放在参数值的位置上

我们看一下漏洞里的指令是怎么执行的

image-20241112222429846

但是如果换成

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

image-20241112222506545

不就无漏洞了吗

转到php的问题上

PHP特别准备了两个过滤函数:

  • escapeshellcmd
  • escapeshellarg

escapeshellcmd() 是用来处理整个命令字符串的,它会在特殊字符前添加反斜杠(\)进行转义,让这些字符失去它们的命令意义。这些特殊字符包括 ;, &, | 等等

escapeshellarg() 主要是用来处理单个参数的,它会把参数包裹在单引号内并转义其中的单引号

二者分工不同,前者为了防止用户利用shell的一些技巧(如分号、反引号等),执行其他命令;

后者是为了防止用户的输入逃逸出“参数值”的位置,变成一个“参数选项”。

但是如果开发者在拼接命令的时候,将$传入参数直接给拼接在“参数选项”的位置上,那用escapeshellarg也就没任何效果了

其他拓展

Java、Python等语言,执行命令的方法相对来说更加优雅:

1
2
3
4
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;

image-20241112223208532

可见,仍然是存在参数注入漏洞的。原因还是例子中说的原因,把query放在了“参数选项”的位置上,无论怎么过滤,或者换成其他语言,都不可能解决问题。

举一反三(周四回来复现下)

参数注入的例子还比较多,因为大部分的开发者都能理解命令注入的原理,但处理了命令注入后,往往都会忽略参数注入的问题。

最典型是案例是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的最早给出的缓解措施也是在拼接点前面加上“–”。