图片第一行显示的 Regular Expression Denial of Service 成功引起了我的注意。Denial of Service 我们都知道,拒绝服务攻击,以一种简单暴力的方式消耗被攻击者的资源,让其服务到达不可用的地步,比如常见的 DDoS。那么 Regular Expression Denial of Service (以下简称 ReDoS) 通过字面意思理解应该就是:正则表达式拒绝服务攻击。
正则表达式的匹配通常是比较耗费计算性能的,尤其是当正则表达式中包含复杂的子表达式的重复匹配、或者重复匹配的简单子表达式也可能匹配另一个子表达式的时候。当一个正则表达式存在这些问题的时候,一些用户的输入可能会导致匹配运行效率非常低下,从而引发 DoS (因为计算资源可能被耗尽)。
从 npm audit 说起
今天尝试用
npm audit
在项目里跑了一下,发现了很多类似下面的错误:Regular Expression Denial of Service (ReDoS)
图片第一行显示的
Regular Expression Denial of Service
成功引起了我的注意。Denial of Service
我们都知道,拒绝服务攻击,以一种简单暴力的方式消耗被攻击者的资源,让其服务到达不可用的地步,比如常见的DDoS
。那么Regular Expression Denial of Service
(以下简称ReDoS
) 通过字面意思理解应该就是:正则表达式拒绝服务攻击。正则表达式的匹配通常是比较耗费计算性能的,尤其是当正则表达式中包含复杂的子表达式的重复匹配、或者重复匹配的简单子表达式也可能匹配另一个子表达式的时候。当一个正则表达式存在这些问题的时候,一些用户的输入可能会导致匹配运行效率非常低下,从而引发 DoS (因为计算资源可能被耗尽)。
Node.js 中的 ReDoS
说了这么多,也许不如一个例子能够把我的意思表达的更清楚:
上面正则表达式看起来非常简单,但是存在我们上面说的可能导致匹配效率低下的问题。可以看到这个简单的匹配耗时大约 5 秒钟,对于 Node.js 这种基于 event loop 的单进程服务器来说是不可接受的。
解决办法
尽量使用社区开源的正则表达式。如果你正在做一些常规的字符串校验,比如邮件、电话号码的匹配,那么可以尝试 validator.js。
如果需要自己编写正则表达式,可以使用 safe-regex 这样的工具来校验自己的正则表达式是否有潜在的问题,也可以使用 regex101 在线进行检验 (regex101 功能十分强大,界面也很清新,另外还提供了 debugger 功能),比如上面的这个例子在 regex101 里会被检测到有 “灾难性的回溯”(Catastrophic backtracking) 问题:
附录
出于好奇,用 Rust 写了跟上面 Node.js 例子中的正则表达式,下面是执行的结果:
Rust 好像对于正则表达式这些潜在的性能问题有一些预防的解决方案 (只调用回溯引擎的混合解决方案),具体我也不是太了解,有兴趣的小伙伴可以自行查资料。不过 Rust 确实是非常快的,真的要抽空好好学习 Rust 咯,后面用得到的地方还是很多的。