escapeshellarg()

(PHP 4 >= 4.0.3, PHP 5, PHP 7, PHP 8)

escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数

定义:escapeshellarg(string $arg): string

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

简单来说,如果输入的内容如果不包含单引号,就直接对输入的字符串添加一对单引号括起来;如果输入内容包含单引号,就先对单引号进行转义再对剩余的字符串添加相应对数的单引号括起来.

test:

1
2
3
4
5
<?php
highlight_file(__FILE__);
$a=$_GET['cmd'];
var_dump(escapeshellarg($a));
?> string(6) ""ab'c""

结果表明:经过escapeshellarg函数处理过的参数被拼接成shell命令,并且被双引号包裹,这样就会造成漏洞

原因:在bash中单双引号解析变量是有区别的。

1
2
3
4
5
6
7
8
9
#例如:
└─# echo `date`
Sat 07 Aug 2021 05:10:12 AM EDT
└─# echo '`date`'
`date`
└─# echo "'`date`'"
'Sat 07 Aug 2021 05:10:25 AM EDT'
└─# echo '"`date`"'
"`date`"

不难发现,在解析单引号时,被单引号包裹的内容中如果存在变量的话,这个变量名是不会被解析成值的,而双引号不一样,bash会将双引号包裹的变量名解析成变量的值再使用。

由以上例子可知,即使参数使用了escapeshellarg函数过滤了单引号,但参数在拼接命令的时候使用了双引号的话还是会导致命令执行漏洞。

escapeshellcmd()

escapeshellcmd — shell 元字符转义

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

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

定义string escapeshellcmd ( string $command)

1
2
3
4
5
6
7
8
9
#例子:
└─# cat test.php
<?php
$a = "`whoami`";
echo escapeshellcmd($a);
?>%

└─# php test.php
\`whoami\`%

同时使用escapeshellargescapeshellcmd会试试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#例子:
└─# cat test.php
<?php
echo escapeshellarg("who'ami")."\n";
echo escapeshellcmd("who'ami")."\n";
echo escapeshellarg("who''ami")."\n";
echo escapeshellcmd("who''ami")."\n";
?>

└─# php test.php
'who'\''ami'
who\'ami
'who'\'''\''ami'
who''ami

可以发现这两个函数对于单引号的处理是有区别的,escapeshellarg函数转义后还会用单引号包裹,但escapeshellcmd函数是直接加一个转义符,对于成对的单引号,escapeshellcmd函数默认不转义,但escapeshellarg函数转义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
└─# cat test1.php
<?php
$param="127.0.0.1' -v -d a=1";
$a=escapeshellarg($param);
$b=escapeshellcmd($a);
$cmd="curl ".$b;
var_dump($a)."\n";
var_dump($b)."\n";
var_dump($cmd)."\n";
system($cmd);
?>


└─# php test1.php
string(25) "'127.0.0.1'\'' -v -d a=1'"
string(27) "'127.0.0.1'\\'' -v -d a=1\'"
string(32) "curl '127.0.0.1'\\'' -v -d a=1\'"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Could not resolve host: 127.0.0.1\
* Closing connection 0
curl: (6) Could not resolve host: 127.0.0.1\

过程分析:

  1. 传入的参数是

    1
    127.0.0.1' -v -d a=1
  2. escapeshellarg函数先对单引号转义,再用单引号将左右两部分括起来起连接作用。所以处理之后效果如下

    1
    '127.0.0.1'\'' -v -d a=1'
  3. 经过escapeshellcmd针对第二步处理之后的参数中的\以及a=1'中的单引号进行处理转义之后的效果如下

    1
    '127.0.0.1'\\'' -v -d a=1\'
  4. 由于第三步处理之后的payload中的\\被解释成了\而不再是转义字符,所以单引号配对连接之后将payload分割为三个部分,具体如下

    1
    2
    3
    4
    └─# curl '127.0.0.1'\\'' -v -d a=1\'
    * Could not resolve host: 127.0.0.1\
    * Closing connection 0
    curl: (6) Could not resolve host: 127.0.0.1\