正则表达式如何导致 ReDoS 漏洞?

1,136 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情

在某些情况下,正则表达式可能会导致系统变慢甚至容易受到 ReDoS 攻击。

当你需要搜索和替换文本时,正则表达式会派上用场。但是,在某些情况下,它们可能会导致系统变慢甚至容易受到 ReDoS 攻击。

介绍

ReDoS 是DoS 攻击的一个子类型。ReDoS 攻击的目的是通过低效的正则表达式停止应用程序或使其变慢。

ReDoS攻击可以分为两种类型:

  1. 带有恶意模式的字符串被传递给应用程序。然后将此字符串用作正则表达式,从而导致 ReDoS。
  2. 将特定格式的字符串传递给应用程序。然后这个字符串被易受攻击的正则表达式评估,也致 ReDoS。

任何 ReDoS 攻击的要点都是在应用程序中使用易受攻击的正则表达式。将特定格式的字符串传递给正则表达式会导致其计算时间过长。

如果 ReDoS 攻击成功,则正则表达式计算会导致灾难性的回溯。这是 Regex 引擎中回溯功能的结果,它会遍历可能的字符串匹配,直到找到正确的字符串。如果没有正确的匹配项,正则表达式将不会停止,直到遍历所有可能的选项。所有可能选项的完整迭代会导致正则表达式的计算时间长得令人无法接受。称为灾难性回溯。

如果正则表达式至少包含一个可能导致大量匹配选项的子表达式,则它很容易受到灾难性回溯的影响。

示例 1

让我们看一个简单的合成示例:

(x+)+y

我们来比较一下两种情况下 (x+)+y 表达式的计算时间:

  1. 正则表达式的输入接受与指定模式一一对应的字符串。同时,每个后续字符串的长度都比前一个字符串长 1。
  2. 正则表达式的输入接受与模式不匹配的字符串(字符串末尾没有y字符)。 同时,每个后续字符串的长度都比前一个字符串长 1。

这样一个实验的结果如下:

1007_ReDoS/image2.png

图 1:字符串匹配模式 (x+)+y时正则表达式的执行时间

1007_ReDoS/image3.png

图 2:如果字符串不匹配 (x+)+y 模式( 末尾缺少y字符),则正则表达式的执行时间。

如图所见,第一组中的字符串被立即处理。然而,第二组的处理量呈指数增长。

问题是,在第一种情况下,正则表达式在第一次尝试时就找到了匹配项。在第二种情况下处理字符串时,一切都变得非常复杂。x+ 模板可以匹配任意数量的x 个字符。 (x+)+模板可以适合由与x+ 对应的一个或多个子字符串组成的字符串。因此,有许多选项可用于将字符串与正则表达式匹配。它们的数量取决于由x 个字符组成的子串的长度。每当正则表达式找不到y字符时,它就会开始检查下一个选项。只有在检查了所有这些之后,正则表达式才会给出答案——没有找到匹配项。

下表显示了xxxx字符串与 (x+)+y正则表达式的几种可能匹配:

1007_ReDoS/image4.png

幸运的是,并非所有正则表达式都容易受到灾难性回溯的影响。如果满足以下条件,则正则表达式容易受到攻击:

  1. 有两个子表达式,其中一个包含另一个。此外,以下量词之一应用于它们中的每一个:*+?+?{...}。在前面的示例中, (x+)+子表达式包括x+
  2. 有一个字符串可以与两个子表达式匹配。例如,xxxx字符串可能同时适合x+(x+)+ 模板。

示例 2

我们发现 (x+)+y表达式是易受攻击的。现在让我们通过添加对另一个字符是否存在的检查来稍微改变它:

(x+z)+y

现在我们有了 (x+z)+ 子表达式,xzxxxxz字符串可以匹配到这个表达式。这个子表达式包含x+ 子表达式,可以对应xxxxx等字符串,可以看到,这些子表达式不能匹配相同的值。因此,不满足第二个条件并且没有灾难性的回溯。

1007_ReDoS/image5.png

图 3:使用一组字符串“破坏”正则表达式的尝试失败。它们中的每一个对应于 x+ 子表达式或 (x+z)+ 子表达式。

如何防止灾难性回溯

选项1 

添加正则表达式处理字符串的执行时间限制。在. net中,我们可以通过在调用静态方法或初始化新的Regex对象时设置matchTimeout参数来实现。

RegexOptions options = RegexOptions.None;
TimeSpan timeout = TimeSpan.FromSeconds(1);
Regex pattern = new Regex(@"newDate\((-?\d+)*\)", options, timeout);
Regex.Match(str, @"newDate\((-?\d+)*\)", options, timeout);

1007_ReDoS/image8.png

图 4:正则表达式的执行时间限制为一秒。

选项 2

使用原子组 (?>...)

Regex pattern = new Regex(@"newDate\((-?\d+)*\)", options, timeout);

对于标记为原子组的表达式,将禁用回溯功能。因此,在所有可能的匹配选项中,一个原子组总是只匹配一个包含最大字符数的子字符串。

虽然原子组是防止灾难性回溯的可靠方法,但我们建议谨慎使用它们。在某些情况下,使用原子组会降低正则表达式计算的准确性。

结论

  1. 正则表达式可能容易受到 ReDoS 攻击,其目的是停止或减慢应用程序;

  2. 由于灾难性的回溯,应用程序变慢了。如果输入字符串与正则表达式匹配的选项数量巨大,并且其中没有正确的选项,则会发生这种情况;

  3. 如果正则表达式包含至少一个可能导致大量匹配选项的易受攻击的子表达式,则它很容易受到灾难性回溯的影响。