ReDoS(正则表达式拒绝服务攻击)理解与实测

1. 引入

很有意思的一种攻击,值得记录一下给自己看。

2. ReDoS(正则表达式拒绝服务攻击)原理

ReDoS 本质是利用正则表达式的灾难性回溯(Catastrophic Backtracking) 实现的拒绝服务攻击。当正则表达式包含大量 "可选路径"(如嵌套的重复量词 */+/?),且输入字符串接近匹配但最终不匹配时,正则引擎会尝试所有可能的路径来验证匹配,导致 CPU 资源被耗尽,服务响应超时甚至崩溃,具体来说:

2.1. 正则回溯匹配

正则引擎(如 JavaScript 的 RegExp、Python 的 re)默认是"贪婪匹配"+"回溯匹配",可以用一个简单例子理解:

正则:/a.*b/(匹配以 a 开头、b 结尾的字符串)

输入:a12345b6789b

引擎的匹配过程:

  1. 先匹配 a → 成功;
  2. .* 是贪婪匹配,会直接匹配到字符串末尾(12345b6789b);
  3. 接下来要匹配 b,但此时已经到字符串末尾,没有字符了 → 匹配失败;
  4. 回溯.* 放弃最后一个字符(b),现在 .* 匹配 12345b6789,再尝试匹配 b → 成功;
  5. 最终匹配结果:a12345b6789b

这个过程就是回溯:当正则的某部分匹配失败时,引擎会"回退一步",调整之前的匹配范围,重新尝试匹配。

正常情况下,回溯次数很少;但如果正则设计不当,回溯次数会呈指数级增长,这就是"灾难性回溯"。

2.2 ReDoS 的核心条件(缺一不可)

触发 ReDoS 必须满足 3 个条件:

  1. 正则包含"嵌套/重叠的重复量词" :如 (x+)+(x*)+x?y* 等(重复量词指 */+/?/{n,});
  2. 输入字符串"接近匹配但最终不匹配":如果输入完全匹配或完全不匹配,回溯次数都很少;只有"差一点匹配"时,才会触发大量回溯;
  3. 重复量词的匹配范围"模糊" :如 .(匹配任意字符)、\w(匹配字母/数字/下划线)等,而非明确的字符范围(如 [a-z])。

2.3 灾难性回溯的过程(经典案例拆解)

以最典型的漏洞正则 /(a+)+b/ 为例,分析输入 aaaaa(无 b,接近匹配但不匹配)的回溯过程:

输入字符 正则部分 匹配尝试 回溯次数
a (a+)+ 匹配 1 个 a → 尝试匹配 b → 失败 1
aa (a+)+ 先匹配 a+a → 尝试 b → 失败;再回溯为 aa → 尝试 b → 失败 2
aaa (a+)+ 尝试 a+a+a → 失败;a+aa → 失败;aa+a → 失败;aaa → 失败 4
aaaa (a+)+ 回溯次数 8 次 8
aaaaa (a+)+ 回溯次数 16 次 16

规律:输入有 n 个 a 时,回溯次数是 2(n−1)2^{(n-1)}2(n−1)(指数级增长)。

  • 当 n=10 → 512 次回溯(可接受);
  • 当 n=20 → 524288 次回溯(轻微卡顿);
  • 当 n=30 → 536870912 次回溯(CPU 100%,程序卡死)。

这就是 ReDoS 的核心:输入长度线性增加,回溯次数指数级增加,最终耗尽系统资源。

2.4 为什么普通正则不会触发 ReDoS?

如果正则设计合理,即使有重复量词,也不会触发灾难性回溯。比如:

  • 安全正则:/a+b/(无嵌套重复);
  • 输入:aaaaa(无 b);
  • 匹配过程:a+ 匹配所有 a → 尝试匹配 b → 失败 → 直接返回 false,无回溯

对比漏洞正则 /(a+)+b/,核心差异是:(a+)+ 让引擎认为"a 可以拆分成多个组",而 a+ 只有一种匹配方式。

2.5 总结

  1. ReDoS 本质:正则引擎的回溯机制在"嵌套重复量词 + 接近匹配的输入"下,产生指数级回溯次数,耗尽 CPU 资源。
  2. 核心触发条件 :嵌套/重叠的重复量词(如 (x+)+)+ 模糊匹配(如 .)+ 接近匹配的输入。
  3. 关键规律 :输入长度线性增长,回溯次数呈 2n2^n2n 指数增长,这是 ReDoS 能"少量输入搞垮服务"的根本原因。

3. 复现

用如下python代码,演示不同输入长度下的匹配耗时:

python 复制代码
import re
import time

vulnerable_re = re.compile(r'^(\w+)+$')

for i in range(1,1000,2):
    t1 = time.time()
    attack_input = 'a' * i + '$'
    vulnerable_re.match(attack_input)
    t2 = time.time()
    print(i, '{0}s'.format(t2-t1) )

实测运行结果输出如下,可以清晰看到:某些情况下,输入长度仅增加 2 倍,耗时却增加上万倍。

复制代码
1 0.0s
3 0.0s
5 0.0s
7 0.0s
9 0.0s
11 0.0s
13 0.0009975433349609375s
15 0.005021333694458008s
17 0.0189666748046875s
19 0.10775160789489746s
21 0.4058842658996582s
23 1.445460557937622s
25 6.071380138397217s
27 23.70104146003723s
29 90.62589955329895s
31 357.1210618019104s
33 1392.2017726898193s

4. 总结

正常匹配时正则引擎的回溯次数极少,而当正则表达式包含嵌套 / 重叠的重复量词(如(x+)+)且遇到接近匹配但最终不匹配的输入时,回溯次数会呈指数级暴增,这种现象就是正则的 "灾难性回溯"。

相关推荐
weixin_433179336 小时前
python - 正则表达式Regex
python·正则表达式
wayz112 天前
正则表达式:从入门到精通
java·python·正则表达式·编辑器
梨落秋霜2 天前
Python入门篇【正则表达式】
python·mysql·正则表达式
吾诺3 天前
Java进阶,时间与日期,包装类,正则表达式
java·mysql·正则表达式
V1ncent Chen4 天前
SQL大师之路 09 模式匹配(正则表达式)
数据库·sql·mysql·正则表达式·数据分析
程序员杰哥5 天前
Jmeter正则表达式提取器和JSON提取器基础用法
自动化测试·软件测试·测试工具·jmeter·正则表达式·json·测试用例
酱紫学Java5 天前
数据安全基础:正则表达式 (Regex) 从入门到实战
python·正则表达式
「QT(C++)开发工程师」6 天前
C++11 新特性 正则表达式、随机数库、元组
c++·正则表达式
椰猫子6 天前
正则表达式和爬虫
正则表达式