PHP利用PCRE回溯次数限制绕过waf
学习文章:https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
前置知识:正则表达式
常见的正则分为DFA(确定性有限状态自动机)和NFA(非确定性有限状态自动关机)
- DFA:从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入
- NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态
NFA执行过程存在回溯,性能劣于DFA,但它支持更多功能,大多数程序语言都是用了NFA作为正则引擎,其中也包括PHP使用的PCRE库
回溯过程解析
示例代码
1 | |
这题中的正则是<?.*[(`;?>].*
- <?:匹配<?
- .*:匹配任意长度的任意字符
- [(`;?>]:字符类,表示匹配其中的任何一个字符
- 修饰符i:不区分大小写
- 修饰符s:让.匹配换行符
假设输入的是<?php phpinfo();//aaaaa,那么实际执行的流程是这样的

见第4步,第一关.*可以匹配任意字符,所以最终能够匹配到输入串的结尾,就是//aaaaa,但这里是不对的,因为第一关.*后面还应该有[(`;?>]字符去匹配
所以这里NFA就开始回溯,先匹配到输入串结尾的前一位,即少匹配一个a,如第5步显示,但仍然匹配不上正则,所以还要继续少匹配一个a,依次类推,直到只匹配到<?php phpinfo(),此时.*匹配的是php phpinfo(),现在后面的;就能匹配上[(`;?>],这样就满足正则表达式的要求,于是不再回溯了,见12步。于是从13步开始就正常向后匹配;,14步又匹配上了.*,这是第二个.*,匹配到了字符串末尾,最后结束匹配
也就是说从5~13步是在回溯的,这里回溯了8次。
简单总结一下,这个回溯机制就是在正常匹配情况下,出现了提前结束匹配,比如.*这种直接匹配到了字符串末尾,则会一个一个字符串的取消匹配直到能正确匹配下一个正则表达。
pcre.backtrack_limit限制利用
PHP为了防止正则表达式的拒绝服务攻击(DOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit,这里可以通过var_dump(ini_get('pcre.backtrack_limit'));的方式来查看当前环境下的上限
回溯次数上限默认是100万,所以果次数超过了100万,preg_match返回的是false,表示这次执行失败了,我们可以调用var_dump(preg_last_error() === preg_backtrack_limit_error);来探查失败的原因(一般都是回溯超出了限制
所以针对上述示例代码,我们可以通过发送超长字符串的方式,使正则执行失败,最后绕过,针对上述代码的poc
1 | |