首页 新闻 会员 周边 捐助

正则占有优先模式在防ReDoS攻击中的作用

0
[已关闭问题] 关闭于 2025-07-26 10:58

占有优先模式在防ReDoS攻击中的作用,实际开发中很有用
ReDoS攻击的本质:灾难性回溯
核心问题: 某些正则表达式在匹配特定构造的恶意字符串时,引擎的回溯次数会呈指数级或阶乘级增长,导致CPU长时间满载(甚至永久阻塞)。

罪魁祸首: 嵌套量词 + 允许回溯的组合。

贪婪模式 (, +, {n,m}) 和 非贪婪模式 (?, +?) 都允许回溯,这是问题的根源。

当正则引擎在尝试匹配一个复杂模式且存在歧义路径时,它会尝试所有可能的回溯路径,导致计算量爆炸。

_java_python的主页 _java_python | 小虾三级 | 园豆:1068
提问于:2025-07-26 10:56
< >
分享
所有回答(1)
0

为什么会导致指数级增长的回溯:

正则表达式:^(a+)+$
目标:匹配由1个或多个a组成的字符串(如"aaa")

结构:外层( )+表示"一组或多组",内层a+表示"一个或多个a"

攻击字符串:"aaaaaaaaX"(8个a + 无效字符X)
匹配过程详解(关键回溯步骤):
第一轮尝试(贪婪匹配):

内层a+吃光所有8个a → 匹配"aaaaaaaa"

外层( )+记录第一组:[aaaaaaaa]

尝试匹配结束符$ → 失败(遇到X)

❗ 开始回溯

回溯:外层尝试第二组:

让第一组吐出1个a → 第一组变为[aaaaaaa](7个a)

剩余字符串:"aX"

外层尝试匹配第二组:

内层a+匹配剩余"a" → 第二组[a]

尝试匹配$ → 失败(遇到X)

❗ 继续回溯

更深层回溯:

让第二组吐出它的a → 第二组变为空

尝试匹配$ → 失败(剩余"aX")

回溯到第一组:再吐出1个a → 第一组[aaaaaa](6个a)

剩余字符串:"aaX"

外层尝试第二组:

选项1:a+匹配"aa" → 第二组[aa] → 匹配$失败(遇到X)

选项2:a+匹配"a" → 剩余"aX" → 匹配$失败

指数级回溯爆发:

引擎必须尝试所有可能的a分组组合:

text
分组方案1: [aaaaaaaa] (1组)
分组方案2: [aaaaaaa] [a] (2组)
分组方案3: [aaaaaa] [aa] (2组)
分组方案4: [aaaaaa] [a] [a] (3组)
分组方案5: [aaaaa] [aaa] (2组)
分组方案6: [aaaaa] [aa] [a] (3组)
分组方案7: [aaaaa] [a] [aa] (3组)
分组方案8: [aaaaa] [a] [a] [a] (4组)
...(继续所有组合)...
每组都要尝试匹配结束符$ → 全部失败

为什么是指数级增长?(O(2^n))
8个a的分组可能 = 2⁷ = 128种组合(相当于7个间隔点每个都有"分/不分"两种选择)

text
示例:aaa aaaa a
↑ ↑ ↑ ↑ (7个可能的分割点)
n个a的分组可能 = 2<sup>n-1</sup>种组合

当n=30时:2<sup>29</sup> = 536,870,912次尝试(5亿多次!)
可视化回溯过程(以4个a + X为例):
text
字符串: "aaaaX"
尝试的分组方案:

  1. [aaaa] → 剩余"X" → 失败
  2. [aaa][a] → 剩余"X" → 失败
  3. [aaa] → 剩余"aX" → 失败(第二组匹配失败)
  4. [aa][aa] → 剩余"X" → 失败
  5. [aa][a] → 剩余"aX" → 失败
  6. [aa] → 剩余"aaX" → 失败(第二组匹配失败)
  7. [a][aaa] → 剩余"X" → 失败
  8. [a][aa] → 剩余"aX" → 失败
    ...共2³=8种组合...
    ➡️ 实际引擎尝试次数远多于8次(因为包含子步骤的回溯)

解决方案:用占有优先模式 ^(a++)+$
内层a++匹配所有a后立即锁定,拒绝回溯

匹配过程:

a++吃光8个a → 锁定

尝试匹配$ → 失败(遇到X)

立即失败,不再尝试其他分组

时间复杂度:O(n)(只扫描一次字符串)

这个例子展示了为什么看似无害的正则表达式可能成为性能炸弹,而占有优先模式是关键的防御手段。

_java_python | 园豆:1068 (小虾三级) | 2025-07-26 10:57
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册