PHP escapeshellarg() 与escapeshellcmd()
最近在对某产品的漏洞进行复现与发散绕过,遇到了一个将escapeshellarg()与escapeshellcmd()一起使用导致命令执行的漏洞,因此就记录一下漏洞成因以及绕过手法。
首先补充一下背景知识,在unix系统中,被单引号包裹的部分如果存在变量,则这个变量不会被解析,但是被双引号包裹的部分在执行时,bash会先将变量名解析为变量的值再使用。
escapeshellarg()的定义:
escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数
功能 :escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,shell 函数包含 exec(),system() 执行运算符(反引号)
定义 :
string escapeshellarg ( string $arg )
由上图可见escapeshellarg()
会为参数加上单引号
另外说一下与escapeshellarg()
类似的php过滤函数escapeshellcmd()
,以下是escapeshellcmd()
的定义:
escapeshellcmd()的定义:
escapeshellcmd — shell 元字符转义
功能:escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到exec()或system()函数,或者执行操作符之前进行转义。
反斜线(\)会在以下字符之前插入: &#;`|\?~<>^()[]{}$, \x0A 和 \xFF\。 \’* 和 “ 仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。
定义 :
string escapeshellcmd ( string $command)
由上图可见escapeshellcmd()函数会将输入的命令中的特殊字符进行转义。
以上两个函数都是PHP自带的过滤函数,这两个函数单独使用时并不会出现什么问题,但是当这两个函数结合使用时,并不会时应用变得更加安全,下面举个例子来证明一下:
假如用户传入的数据为192.168.64.2 -v -d a=1
:
可以看到192.168.64.2 -v -d a=1
经过escapeshellarg()函数处理之后加上了单引号:
1 | 192.168.64.2 -v -d a=1 |
由于处理后的字符串中单引号可配对,且无满足escapeshellcmd()函数转义的其他特殊符号,所以处理后的结果不变:
1 | '192.168.64.2 -v -d a=1' |
因此实际执行的命令为:
1 | curl '192.168.64.2 -v -d a=1' |
结合上文内容,bash中被单引号包裹的变量不会被解析为值,所以-v -d a=1
不会被解析为参数,因此没有参数逃逸的情况发生。
但是如果用户输入的数据中加入了奇数个单引号,情况就不一样了,假如用户输入数据为 192.168.64.2 ' -v -d a=1
,实际的处理过程如下:
由于字符串中存在一个单引号,因此escapeshellarg()函数会转义其中的单引号并用成对的单引号将转义部分两侧的字符串连接起来:
1 | 192.168.64.2 ' -v -d a=1 |
但是经过escapeshellcmd()函数时,escapeshellcmd()将特殊字符\
进行转义变为了\\
,同时识别到最后一个单引号未闭合,因此也为最后一个单引号添加转义符号成为\'
1 | '192.168.64.2 '\'' -v -d a=1' |
因此经过escapeshellarg()与escapeshellcmd()函数处理之后实际执行的命令为:
1 | curl '192.168.64.2 '\\'' -v -d a=1\' |
闭合的双引号部分可以省略,\\
被bash识别为\
,因此这个命令可简化为如下形式:
1 | curl '192.168.64.2 '\\'' -v -d a=1\' |
本命令即为向192.168.64.2 \
发起请求,请求的数据为POST的a=1,因此造成了参数逃逸。