0x00 前言
起源于CODEBREAKING中一个ctf题目 easy - pcrewaf1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}
if(empty($_FILES)) {
die(show_source(__FILE__));
}
$user_dir = 'data/' . md5($_SERVER['REMOTE_ADDR']);
$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) {
echo "bad request";
} else {
@mkdir($user_dir, 0755);
$path = $user_dir . '/' . random_int(0, 10) . '.php';
move_uploaded_file($_FILES['file']['tmp_name'], $path);
header("Location: $path", true, 303);
}
0x01 题目分析
题目大意为向服务器写入一个php文件,但是文件内容收到了限制1
preg_match('/<\?.*[(`;?>].*/is', $data)
正则匹配了php标签,也就是说我们无法写入php代码,常规绕过思路是使用ASP标签<% %>
或者script PHP标签<script language="php">
来区分php代码,但是题目环境使用php7版本,这两种标签均被移除。
这里,PHITHON大神提到了一种很好的方式:PHP利用PCRE回溯次数限制绕过某些安全限制
0x02 利用pcre.backtrack_limit限制绕过pcrewaf
常见正则有两种匹配形式
- DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入
- NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态
而php所使用的pcre库则采用了NFA方式
那么何为回溯呢?这里以此题的正则为例匹配<?php phpinfo();//booglewadwdadadwad
1
/<\?.*[(`;?>].*/is
当其执行到第一个.*
时,它会匹配到后面所有的内容
而继续执行到 [(;?>]
时,前面匹配的全部内容显然不符合,此时便会进行回溯。直到匹配到;
时符合条件
那么这里便有一个问题,如果回溯时无关字符无限长,那么岂不是会导致正则永远无法执行完成,导致正则dos。
其实为了防止这种情况的发生,pcre对回溯次数做了限制,默认配置回溯次数pcre.backtrack_limit
为1000000次。
当回溯次数超过一百万次时,会返回false
,那么这个题目便可构造超长的回溯次数使其返回false
绕过文件内容检查。
0x03 payload
1 | import requests |
0x04 修复方法
题目中使用了preg_match
对字符串匹配,采用===
判断返回值1
2
3if(is_php($input) === 0) {
...
}