关于PHP正则回溯次数绕过
写在最前面
刚好写题遇到了有关PHP正则回溯的知识,在此系统的学一下并写笔记。参考了如下这些师傅的文章:
关于PHP的正则回溯
关于PHP的正则回溯绕过对于PHP中所有采用正则匹配的函数均是适用的,根据PHP官方文档有如下这些函数;
PHP中正则匹配在匹配时遵守着具体的匹配限制,如果在匹配超出限制时(即匹配发生错误)结果会返回False,如preg_match函数:
如preg_match函数通常会被用于作为关键字检测,只有未被检测时才能正常执行,这就意味着如果我们可控,我们可以调整PHP设置中关于正则匹配的限制,同时构造特定的字符串使正则匹配超出限制,从而使preg_match函数返回False,这样在对preg_match函数返回值未使用强等于(===)判断的情况下我们便绕过了关键字检测。这就意味着在一些依托正则匹配设置WAF的情况下,如果可控正则匹配的限制,我们便可以利用正则回溯绕过,使用出题人不想让我们使用的相关方法,做到出其不意。
(PHP中关于正则匹配的设置)
其中pcre.backtrack_limit是正则匹配时的回溯次数,pcre.recursion_limit是正则匹配时的递归空间,pcre.jit是正则匹配启用jit编译。
我们这里讲述的使得正则匹配超出限制的方法就是回溯超限,先来看下PHP中正则匹配在什么时候会触发回溯,接着再来利用。
贪婪匹配
贪婪匹配回溯出现于贪婪匹配过多的情况。如匹配串为aaab,匹配子串为.*b,匹配过程如下:
可以见到在第二次匹配时.*匹配到了整个原字符串aaab,已匹配的字符串变成aaab;但在第三次匹配时b未匹配到字符,故回溯一次,已匹配的字符串变成aaa;第四次匹配时b匹配到了原字符串末尾的b,已匹配字符串变成aaab,此时匹配字串匹配完成。
在此过程中.*贪婪匹配过多而匹配了整个字符串就导致了随后的b无法匹配到字符,这样为了确保已经匹配到的字符串中的b字符不被漏匹配便进行了回溯直到匹配上或匹配失败。
(以下是匹配失败(即b不存在时)的情况,可见回溯次数大大增多了)
非贪婪匹配
和贪婪匹配区别在于,贪婪匹配注重宁可不匹配也不匹配多的原则,所以匹配时会先对非贪婪匹配随后的字符匹配,匹配不上再回溯进行非贪婪匹配。如匹配串为aaab,匹配字串为.*?b,匹配过程如下:
从第一次匹配开始,按照非贪婪匹配的原则先采用b匹配,b匹配不上再回溯对.*?进行非贪婪匹配,故第一个a将会被匹配上;第二次匹配时,已匹配的字符串变成a,此时匹配仍先采用b匹配,匹配不上再回溯对.*?进行非贪婪匹配,故第二个a将会被匹配上;直到第六次匹配时,b成功匹配,整个匹配结束共进行了三次回溯。
实际中的PHP正则回溯
然而实际上PHP中的正则回溯的次数是比我们上面统计的要多的,这与PHP的函数逻辑有关(Orz,太菜了底层代码一点都看不懂)。例如:
按照此前的统计应该是3次回溯,但实际上确需要4次回溯。
还有jit编辑器,当jit编辑器(默认开启)关闭时某些原本不需要回溯的正则匹配会变得需要回溯。例如:
这就表示在贪婪模式和非贪婪模式之外采用字符域[]也能明确触发回溯。
如何使用
对于我们来说可控大概是可以上传.htaccess文件(不用重启服务器是真的方便),在.htaccess中重写正则相关的限制(既然是要靠回溯超限触发,那就干脆把超限限制设置到最低咯,并且实际上除了上文所述的以外仍有其他PHP正则匹配式可以触发回溯(博主太菜没得出个通用的触发回溯的规律Orz))。
接着再正常使用那些会被WAF检测的语句就好了,如果符合条件WAF就会被我们绕过了。
写在最后面
在写这篇文章的时候对正则的相关设置经行了一些手工测试,在测试过程中发现得到的结果并不所有的都是准确的或可以解释的(如同一套正则对字符a和字符b得不到同样的匹配结果???;在PHP代码中对pcre.jit的开启和关闭并不是及时的,往往等上个十几秒再刷新才会有反应......),写在文中的是选取的多次测试仍不变的结果(相对来说准确)。如果文章有不对之处或不足之处,欢迎各位师傅斧正。