正则回溯爆炸:一个表达式把 CPU 打满,3 步改写救回服务

举报
deli007 发表于 2026/09/23 17:25:14 2026/09/23
【摘要】 正则回溯爆炸:一个表达式把 CPU 打满,3 步改写救回服务

一个看起来逻辑正常的正则,遇到恶意或异常长的输入,可能从几毫秒变成几十秒,直接把服务的 CPU 顶到 100%。这篇文章用本机跑出来的数据说明回溯爆炸是怎么发生的,并给出 3 个能直接落地的修复方法。

为什么说这是“隐蔽雷”

正则引擎大多采用回溯(backtracking)来处理可选分支。Pattern 里出现一个“量词包量词”时,引擎会在不同拆法之间反复试,路径数随输入长度指数增长。

最经典的坏味道是这个:

import re

pattern = re.compile(r"^(a+)+$")

它想表达“整串都是 a”。但如果输入是 aaaaaaaa...X,结尾多一个不匹配的 X(a+) 和外面这个 + 就会把前面几十个 a 拆成无数种组合,每一种都要推到结尾才发现失败。字符串长度每加 1,尝试次数就接近翻一倍。

复现:同一段匹配,耗时怎么涨

我把下面这段在 Python 3.14 本机跑了一遍,改变 n 观察耗时:

import re, time

def match_time(n: int):
    s = "a" * n + "X"
    t = time.perf_counter()
    re.match(r"^(a+)+$", s)
    return time.perf_counter() - t

for n in (20, 22, 24, 26):
    print(n, round(match_time(n), 4))
a 的个数 本机耗时(秒)
20 0.0392
22 0.1565
24 0.6256
26 2.5148

每多 2 个字符,耗时约变为 4 倍;套成单字符就是约 2 倍。按这个趋势,n=30 会到几十秒,n=36 就会上千秒。像 (a+)+$ 这类模式,只要输入长度不受控,就是线上定时炸弹。OWASP 等安全资料里把这类问题统称为 ReDoS。

3 步改写救回服务

第一步:去掉嵌套量词,回到单一匹配路径。

同样的“整串都是 a”,直接写成线性模式:

import re

# 原来:会指数回溯
re.fullmatch(r"(a+)+", s)

# 改后:一次扫到尾
re.fullmatch(r"a+", s)

大多数 ReDoS 都能靠这步解决:把 (a+)+([a-z]+)+(\d+)+ 这种“一个字符组加一层量词,外面再套一层量词”改成一层。

第二步:能改写就改写,改不了就限制输入长度。

有些复杂 pattern 不能简化,那就先卡住输入:if len(text) > 512: reject。安全资料里的建议也类似——不可信的输入,在做复杂解析前先限长。宁可拒绝“过长”,也别让一个请求把整台机器拖住。

第三步:给匹配加超时,防住漏网之鱼。

Python 标准库的 re 没有内置超时,这也是 ReDoS 在 Python 服务里容易失控的原因。第三方 regex 包提供了 timeout 参数:

import regex

try:
    regex.match(r"^(a+)+$", bad_text, timeout=1)
except TimeoutError:
    # 1 秒没匹配完就当失败处理,不再死等
    pass

如果不想引入新依赖,就把正则放进工作进程执行,主进程设个超时把它干掉。核心思路都一样:匹配可以被拒绝,但服务不能被一个表达式按死。

结果与下一步

这一轮跑完得到三个能直接带走的结论:

  • ^(a+)+$ 这类“量词包量词”是本机实测的指数坑,n 从 20 到 26,耗时从 0.04 秒涨到 2.5 秒。
  • 优先去嵌套量词改写,其次限制输入长度,最后用 timeout 兜底。
  • 看到网上复制来的复杂正则,先扫一眼有没有两层量词套同一个字符组。

Podcast 里常听到“一个正则让服务挂了”,真正落地处理时别只补小时段重启,先查 pattern 本身是不是 ReDoS。把这三步写进 code review 检查项,比自己记几个零散案例更省心。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。