为什么邪恶的正则表达式是个问题?
因为计算机完全按照你告诉他们的去做,即使这不是你的意思或者完全不合理。如果您要求正则表达式引擎证明,对于某些给定的输入,给定模式匹配或不匹配,那么无论必须测试多少不同的组合,引擎都会尝试这样做。
这是一个简单的模式,灵感来自 OP 帖子中的第一个示例:
^((ab)*)+$
给定输入:
abababababababababababab
正则表达式引擎尝试类似的东西,(abababababababababababab)
并在第一次尝试时找到匹配项。
但随后我们把活动扳手扔进去:
ababababababababababababa _
引擎将首先尝试(abababababababababababab)
,但由于额外的a
. 这会导致灾难性的回溯,因为我们的模式出于(ab)*
善意,将释放它的一个捕获(它将“回溯”)并让外部模式再次尝试。对于我们的正则表达式引擎,它看起来像这样:
(abababababababababababab)
- 没有
(ababababababababababab)(ab)
- 没有
(abababababababababab)(abab)
- 没有 - 没有
(abababababababababab)(ab)(ab)
- 没有
(ababababababababab)(ababab)
- 没有
(ababababababababab)(abab)(ab)
- 没有
(ababababababababab)(ab)(abab)
- 没有
(ababababababababab)(ab)(ab)(ab)
- 没有
(abababababababab)(abababab)
- 没有
(abababababababab)(ababab)(ab)
- 没有
(abababababababab)(abab)(abab)
- 没有
(abababababababab)(abab)(ab)(ab)
- 没有
(abababababababab)(ab)(ababab)
-
(abababababababab)(ab)(abab)(ab)
没有 -
(abababababababab)(ab)(ab)(abab)
没有 -
(abababababababab)(ab)(ab)(ab)(ab)
没有 -
(ababababababab)(ababababab)
没有 -
(ababababababab)(abababab)(ab)
没有 -
(ababababababab)(ababab)(abab)
没有 -
(ababababababab)(ababab)(ab)(ab)
没有 -
(ababababababab)(abab)(abab)(ab)
没有 - 没有
(ababababababab)(abab)(ab)(abab)
- 没有
(ababababababab)(abab)(ab)(ab)(ab)
- 没有
(ababababababab)(ab)(abababab)
- 没有
(ababababababab)(ab)(ababab)(ab)
- 没有
(ababababababab)(ab)(abab)(abab)
- 没有
(ababababababab)(ab)(abab)(ab)(ab)
- 没有
(ababababababab)(ab)(ab)(ababab)
- 没有
(ababababababab)(ab)(ab)(abab)(ab)
- 没有
(ababababababab)(ab)(ab)(ab)(abab)
- 没有
(ababababababab)(ab)(ab)(ab)(ab)(ab)
- 没有
...
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abababab)
- 没有
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ababab)(ab)
- 没有
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)(abab)
- 没有
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)(ab)(ab)
- 没有
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ababab)
- 没有
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)(ab)
- 没有
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)
- 没有
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)
- 没有
可能组合的数量随着输入的长度呈指数增长,并且在你知道之前,正则表达式引擎正在消耗你所有的系统资源试图解决这个问题,直到用尽所有可能的术语组合,它最终放弃并报告“没有匹配”。与此同时,你的服务器变成了一堆燃烧的熔融金属。
如何发现邪恶的正则表达式
这实际上非常棘手。现代正则表达式引擎中的灾难性回溯在本质上类似于Alan Turing 证明无法解决的停机问题。我自己写了有问题的正则表达式,尽管我知道它们是什么以及通常如何避免它们。将你能做的一切都包装在一个原子组中有助于防止回溯问题。它基本上告诉正则表达式引擎不要重新访问给定的表达式 - “锁定第一次尝试匹配的任何内容”。但是请注意,原子表达式不会阻止表达式内的回溯,因此^(?>((ab)*)+)$
仍然很危险,但^(?>(ab)*)+$
很安全(它会匹配(abababababababababababab)
然后拒绝放弃任何匹配的字符,从而防止灾难性的回溯)。
不幸的是,一旦编写完成,实际上很难立即或快速找到有问题的正则表达式。最后,识别错误的正则表达式就像识别任何其他错误代码一样- 它需要大量时间和经验和/或单个灾难性事件。
有趣的是,自从第一次写出这个答案以来,德克萨斯大学奥斯汀分校的一个团队发表了一篇论文,描述了一种能够对正则表达式进行静态分析的工具的开发,其明确目的是发现这些“邪恶”模式。该工具是为分析 Java 程序而开发的,但我怀疑未来几年我们会看到更多工具围绕分析和检测 JavaScript 和其他语言中的问题模式而开发,尤其是在ReDoS 攻击率持续攀升的情况下。
静态检测使用正则表达式的程序中的 DoS 漏洞
Valentin Wüstholz、Oswaldo Olivo、Marijn JH Heule 和 Isil Dillig
德克萨斯大学奥斯汀分校