防止 JavaScript 中的正则表达式回溯

防止 JavaScript 中的正则表达式回溯

正则表达式是用于在软件应用程序中操作和验证文本的强大工具。然而,某些正则表达式模式可能容易受到回溯的影响,这可能会导致超线性运行时,并可能导致DoS攻击。在本文中,我们将探讨什么是回溯、它如何导致性能问题以及如何在正则表达式中防止回溯。

正则表达式中的回溯是什么

回溯是正则表达式引擎用来处理包含可选或重复子模式的复杂模式的技术。当正则表达式模式包含可选或重复的子模式时,引擎可能需要尝试子模式的多种组合才能找到匹配项。这个过程称为回溯。

例如,有以下正则表达式:

/^[a-zA-Z0-9\s]+$/

此正则表达式应匹配仅包含字母数字字符和空格的任何字符串。然而,它很容易受到回溯的影响,因为+字符类后面的运算符允许字符类的任意数量的重复。

攻击者可以通过发送包含一长串不匹配字符的恶意搜索查询来利用此漏洞,例如:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaab

该字符串包含 30 个a字符,后跟一个b. 当正则表达式引擎尝试匹配该字符串时,它将前 30 个a字符与字符类匹配,但无法匹配b字符。然后,引擎将回溯并尝试字符类的不同组合,直到匹配整个字符串或耗尽所有可能的组合。

在本例中,字符串中有 31 个字符,因此有 2 种可能的字符类组合可供尝试。这可能需要很长的时间,可能会导致服务器遭受 DoS 攻击。

为了防止此漏洞,您可以修改正则表达式以使用*运算符代替+运算符,如下所示:

/^[a-zA-Z0-9\s]*$/

使用*可以使正则表达式不易受到回溯并降低 DoS 攻击的风险,因为它减少了正则表达式引擎需要探索的可能路径的数量。

+的意思是“一个或多个”,而*的意思是“零个或多个”。使用+时,正则表达式引擎必须在放弃之前尝试与模式匹配的所有可能的字符组合。这可能会导致回溯并导致引擎花费过多的时间来尝试匹配字符串,从而更容易受到 DoS 攻击。

另一方面,使用*使子模式成为可选,这意味着如果不匹配,正则表达式引擎可以完全跳过它。这减少了引擎需要探索的可能路径的数量。

回溯如何导致性能问题

回溯可能会通过两种方式导致性能问题:

  1. 超线性运行时间:回溯可能会导致正则表达式模式的运行时间变得超线性,这意味着匹配模式所需的时间增长速度快于输入字符串的长度。这可能会使该模式对于长输入字符串极其缓慢,并且如果将该模式应用于不受信任的用户输入,则可能会导致 DoS 攻击。
  2. 高内存使用量:回溯还会导致正则表达式引擎使用大量内存,特别是当模式包含嵌套的可选或重复的子模式时。这可能会导致应用程序内存不足并崩溃。

如何防止正则表达式模式中的回溯

为了防止正则表达式模式中的回溯,我们需要以避免可选或重复子模式的方式设计思路。以下是我们可以使用的一些技巧:

使用特定的字符类:

使用特定的字符类可以通过限制可以匹配子模式的字符数来帮助防止回溯。例如,/[a-z]/匹配从 az 的任何小写字母。

使用非捕获组

使用非捕获组可以通过避免不必要的内存分配来帮助防止回溯。例如,/(?:ab)+/匹配字符串ab的一次或多次出现,而不创建捕获组。

使用原子组

使用原子组可以通过使子模式成为非可选来帮助防止回溯。例如,/a(?>b|c)/匹配包含字母a后跟bc的字符串,而不进行回溯。

使用多个子模式而不将其中任何一个设为可选

使用多个子模式而不将其中任何一个设为可选可以通过限制正则表达式引擎需要探索的可能路径的数量来帮助防止回溯。例如,/^(ab|cd|ef)$/匹配abcdef字符串,而不进行回溯。

使用所有格子模式:

使用所有格子模式可以通过使子模式成为非可选来帮助防止回溯。所有格子模式由语法(?+...)表示。例如,/a(?+b)/匹配包含字母a后跟字母b的字符串,而不进行回溯。

使用惰性量词:

使用惰性量词可以通过使子模式可选来帮助防止回溯,但仍然可以防止回溯。惰性量词由+?or*?符号表示。例如,/a+?/匹配一​​次或多次出现的字母a,但使用惰性量词来防止回溯。

使用有界量词:

使用有界量词可以通过限制子模式的重复次数来帮助防止回溯。有界量词由语法{min,max}表示,其中minmax是指定最小和最大重复次数的整数。例如,/a{1,3}/匹配包含重复一到三次的字母a的字符串,而不回溯。

你可能感兴趣的:(javascript,前端,javascript,正则表达式)