提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
今天进行CTF刷题的时候,刷到了关于正则表达式贪婪匹配后进行回溯产生的绕过,记录一下。
提示:以下是本篇文章正文内容,下面案例可供参考
一、PCRE绕过的原理
正则表达式是一个可以被“有限状态自动机”接受的语言类。
有限状态自动机”,其拥有有限数量的状态,每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移。
而常见的正则引擎,又被细分为DFA(确定性有限状态自动机)与NFA(非确定性有限状态自动机)。他们匹配输入的过程分别是:
- DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入
- NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态
由于NFA的执行过程存在回溯,所以其性能会劣于DFA,但它支持更多功能。大多数程序语言都使用了NFA作为正则引擎,其中也包括PHP使用的PCRE库。
比如说:
<?php
function pcre($data){
return preg_match('/<\?.*[;].*/is', $data);
}
假如传入$data=<?phpinfo();//aiwin 函数pcre进行匹配时,由于.*进行贪婪匹配,所以会匹配掉<?phpinfo();//aiwin 整个字符串,但是此时不对,因为正则表达式显示.*后还有字符; 所以NFA会开始进行回溯,每一步回溯一个字符,即第一步先回溯n,直到回溯匹配到; 一共回溯8步。
问题在于: PHP为了防止对正则表达式进行拒绝服务攻击,设置了pcre.backtrack_limit的配置,限制正则表达式的回溯次数:
查看回溯次数,默认上限为100万次,当回溯次数超过100万次,就会使preg_match函数返回false,说明此次正则匹配执行失败。
防御方法:要防御正则表达式的回溯次数溢出,将preg_match的结果使用强等于===匹配即可。
二、例题
1.[NISACTF 2022]middlerce
题目源码:
<?php
include "check.php";
if (isset($_REQUEST['letter'])){
$txw4ever = $_REQUEST['letter'];
if (preg_match('/^.*([\w]|\^|\*|\(|\~|\`|\?|\/| |\||\&|!|\<|\>|\{|\x09|\x0a|\[).*$/m',$txw4ever)){
die("再加把油喔");
}
else{
$command = json_decode($txw4ever,true)['cmd'];
checkdata($command);
@eval($command);
}
}
else{
highlight_file(__FILE__);
}
?>
1,题目从头开始进行正则表达式匹配,.*贪婪匹配后又匹配括号里的字符,最后再.*后结束
checkdata函数进行的是黑名单过滤,可以使用tail以及通配符*进行获取flag,过滤了
/\^|\||\~|assert|print|include|require|\(|echo|flag|data|php|glob|sys|phpinfo|POST|GET|REQUEST|exec|pcntl|popen|proc|socket|link|passthru|file|posix|ftp|\_|disk|tcp|cat|tac/i
2,代码虽然执行了eval函数,但是却不会将结果输出到页面,可以使用短标签代替echo的效果进行输出,<??>和<?=?>。<??>相当于对<?php>的替换。而<?=?>则是相当于<? echo>,<??>写法需要开启short_open_tag,<?=?>则是默认开启。
3,关键点在于使回溯次数超过100万次,返回false,利用脚本上传post数据:
data = '{"cmd":"?><?= `tail /f*`?>", "#":"' + "#" * 1000000 + '"}'
其中.*会匹配掉整个字符串,但是此时不正确,因为后面括号应该还有字符,会进行回溯,传入一百万个#会使回溯次数上限,返回false绕过正则匹配。