ReDoS 是什么 - 正则表达式灾难性回溯与检测方法

1944 字
10 分钟
ReDoS 是什么 - 正则表达式灾难性回溯与检测方法

🌟 一个正则怎么会”卡死”程序#

有些正则,在正常输入下瞬间完成,但换一段特定文本后,会慢到让程序假死。这不是 bug,而是灾难性回溯(Catastrophic Backtracking)——正则引擎在尝试所有可能的分支组合,耗时随输入长度指数级增长。

攻击者可以利用这一点,用一小段恶意文本让你服务器 CPU 打满,这就是 ReDoS(Regular Expression Denial of Service,正则表达式拒绝服务)。


🧠 为什么会产生灾难性回溯#

正则引擎(回溯型 NFA)在匹配失败时会”回溯”——退回去尝试其他分支。当表达式里存在量词嵌套量词、相邻的歧义量词或可选的重复结构时,回溯的分支数量会爆炸式增长。

经典危险写法:

^(a+)+$

对输入 aaaaaaaaaaaaaaaaaaaaaaaaX,引擎会穷举”如何把这段 a 分组”的所有方式,数量随 a 的个数指数增长。

危险结构清单#

结构例子风险
嵌套量词(a+)+、(a*)*高
相邻歧义量词a*a*、\d+\d+高
可选重复(a|aa)+高
回溯量词 + 长尾失败(.*)*、(\w+\s?)*高
嵌套可选(a?)*中

🔍 如何检测#

  1. 静态判断:看正则里是否有”量词套量词""相邻歧义量词”。有就要警惕。
  2. 实测计时:用一段”几乎匹配但最后不匹配”的长文本测试,观察耗时是否随长度急剧上升。
  3. 工具辅助:星点性能与安全工具提供复杂度分析、回溯分析与 ReDoS 启发式检测,并可对大文本做可中止的压力测试。

说明:ReDoS 检测多为启发式判断,结论应结合具体输入与引擎,不能当作绝对定论。

自测小实验#

在正则测试器里输入正则 ^(a+)+$,测试文本写一长串 a 后面跟一个 X(不匹配)。如果文本越长页面越卡,就说明存在灾难性回溯。


🛠 如何修复#

问题写法改进方向
(a+)+去掉多余嵌套,写成 a+
a*a*合并为 a*,或让两部分互斥
(.*)*去掉外层量词
(\w+\s?)*改为不重叠的写法,或使用原子组/占有量词(部分引擎支持)
复杂长正则拆成多步,或先做简单校验再细化

核心原则:让引擎没有”歧义分支”可回溯——各部分尽可能互斥、不重叠。

其他缓解手段#

  • 加锚点:^ $ 限定位置,减少无效尝试;
  • 限定字符集:用 [0-9] 代替 .;
  • 限制输入长度:对用户输入先做长度上限;
  • 用非回溯引擎:如 RE2(Go)不支持反向引用与部分断言,天然规避;
  • 原子组/占有量词:(?>...)、a++(部分引擎支持)。

🌐 各引擎差异#

引擎回溯型原子组/占有量词
JavaScript是部分(现代)
Python re是否(regex 库支持)
Java是支持
PCRE是支持
Go(RE2)否—(无回溯)

用多语言引擎核对特性支持。


🛡 工程实践#

  • 对用户可控的正则(如自定义规则)做白名单/长度限制;
  • 上线前用性能与安全做一次检测;
  • 大文本处理放在可中止的流程里,避免页面/服务卡死;
  • 关键服务可设置超时。

❓ 常见问题(FAQ)#

所有慢正则都是 ReDoS 吗? 不一定。处理超长文本本身也会慢。ReDoS 特指"回溯分支指数增长"导致的急剧变慢。
后端语言也有这个问题吗? 有。只要使用回溯型正则引擎(如 Java、Python、PHP 的默认引擎),都可能触发。
换成 DFA 引擎就没问题了吗? DFA/RE2 不做回溯,天然规避这类问题,但功能(如反向引用、部分断言)受限。
怎么快速排查现有正则? 把正则贴进[性能与安全](https://www.xingdian.net/app/regex/performance/),看复杂度与 ReDoS 提示。
懒惰匹配能避免回溯吗? 不一定。懒惰减少匹配范围,但某些结构仍会回溯,需实测。
正则会卡死浏览器吗? 可能。所以大文本测试要用"可中止"的方式,或用[性能与安全](https://www.xingdian.net/app/regex/performance/)分块测试。
输入长度有限制就没问题吗? 长度限制能缓解,但指数增长下短输入也可能很慢,仍应优化结构。
怎么衡量正则复杂度? 看分组数、嵌套层级、分支数、量词数、潜在回溯结构,[性能与安全](https://www.xingdian.net/app/regex/performance/)会给出报告。

🧪 更多危险模式与修复对照#

危险写法危险原因安全替代
^(a+)+$分组内量词 + 外量词嵌套^a+$
^(a|aa)+$分支前缀重叠^a+$
^(\d+)*$嵌套量词^\d+$
(\w+\s?)*可选 + 重复,产生歧义明确分隔,如 (\w+\s)*\w+
.*.*.*多个歧义 .*合并为 .*
([a-zA-Z]+)*嵌套量词[a-zA-Z]+
^([^,]+)*$字符类量词嵌套明确分隔符

判断口诀:如果一个量词作用在一个”本身可重复”的分组上((...+)*、(...*)*、(...+)+),就要警惕。


🧠 回溯原理详解#

以 (a+)+$ 匹配 aaaaX 为例(X 不匹配):

  1. 外层 + 先让内层 a+ 吃尽量多的 a;
  2. 遇到 X 无法匹配 $,引擎回溯;
  3. 内层退一个 a,外层再试;
  4. 由于外层和内层都能”分配”这些 a,组合方式多达 2^(n-1) 种;
  5. n 稍大,尝试次数就爆炸。

关键问题:外层和内层对同一段字符都有”解释权”,导致重复尝试。消除方法是让它们不重叠(如直接 a+)。


🛡 服务端防护清单#

  • 对用户可控的正则做长度上限与白名单;
  • 对输入文本做长度上限;
  • 关键正则做超时/可中止处理;
  • 上线前用性能与安全扫描;
  • 高并发场景优先使用 RE2/DFA 引擎;
  • 代码评审时检查 (...+)* 类结构。

📊 复杂度与回溯的量化#

结构分组数嵌套回溯风险
\d+00无
(\d+)11低
(\d+)+12高
(\d+)*12高
`(aab)+`12
(\w+\s?)*12高

判断要点:量词是否作用在”本身可重复”的元素上。是,则有回溯风险。


🧰 相关工具#


🧭 更多常见问题#

检测结果一定准确吗? 不一定。多为启发式判断,需结合具体输入与引擎;建议配合实测。
为什么我的正则平时很快? 回溯爆炸只在特定"接近匹配但最终失败"的输入上触发,平时可能没遇到。
原子组怎么用? `(?>...)` 表示匹配后不回溯,能消除部分歧义(部分引擎支持)。
大文本测试会不会把页面卡住? 用[性能与安全](https://www.xingdian.net/app/regex/performance/)的"可中止"压力测试,避免真的卡死。
怎么给正则加超时? 多数语言的正则库不支持超时,需放到独立线程/进程并设限,或改用 RE2。
ReDoS 只在正则里发生吗? 主要发生在回溯型正则引擎;其他回溯型解析(如某些模板引擎)也可能有类似问题。

💬 总结#

ReDoS 的本质是”歧义结构 + 回溯爆炸”。写正则时避免量词嵌套、相邻歧义量词,必要时拆分或改互斥写法;上线前用性能与安全做一次检测。

立即体验:访问 性能与安全

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

最后更新于 2026-09-13,距今已过 32 天

部分内容可能已过时

相关文章 智能推荐
1
正则性能优化 - 避免回溯、提升匹配速度的方法
正则工具 正则匹配很慢怎么办?本文总结正则性能优化方法:避免灾难性回溯、减少歧义量词、合理使用锚点、避免不必要的分组与捕获、预编译复用等,并说明 ReDoS 防范。附在线性能检测工具。
2
正则可视化 - 用结构图看懂正则表达式的分组与分支
正则工具 正则可视化工具把正则表达式画成结构图:分组、分支、量词、字符类、断言一目了然。适合学习和调试复杂正则,在线免费。
3
正则表达式转代码 - 转 JavaScript/Python/Java/Go/PHP
正则工具 正则怎么用到代码里?本文给出 JavaScript、Python、Java、Go、PHP、C# 的写法模板(含转义、Flags、命名组),并介绍在线正则转代码工具,一键得到可复制的代码示例。
4
正则替换怎么用 - 在线正则表达式批量替换工具
正则工具 正则替换怎么用?在线正则表达式替换工具支持 $1、$&、$<name> 等替换引用,实时显示替换前后文本与替换次数。含日期改写、去空格、加前缀、键值重排等 10+ 场景示例与 FAQ,浏览器本地处理。
5
正则表达式生成器 - 按条件或示例在线生成正则
正则工具 不会写正则?用正则生成器按条件生成:字符类型、长度、前缀后缀、数字范围、固定结构;也能从示例反推候选正则。在线免费,浏览器本地处理。
随机文章 随机推荐
公告
🎉 已升级 2.0 版本,欢迎使用。
分类
标签
站点统计
文章
197
分类
24
标签
747
总字数
334,099
运行时长
0 天
最后活动
0 天前

目录