Python正则表达式中使用用户输入是否安全?

16

我希望让我的用户在一些功能上使用正则表达式。我想知道将用户输入传递给re.compile()的影响。我认为没有办法让用户提供一个字符串来执行任意代码。我想到的风险如下:

  1. 用户可能会输入导致异常的内容。
    • 用户可能会输入导致正则表达式引擎花费很长时间或使用大量内存的内容。

对于第一种情况,解决方法很简单:捕获异常。我不确定是否有好的解决方案来解决第二种情况。也许只限制正则表达式的长度就可以了。

还有其他需要担心的问题吗?

6个回答

22

我曾经开发过一个程序,允许用户输入自己的正则表达式。你是对的 - 他们可以(也确实)输入需要很长时间才能完成的正则表达式 - 有时甚至超过了宇宙的寿命。更糟糕的是,在处理正则表达式时,Python会持有GIL,因此不仅会挂起运行该正则表达式的线程,还会挂起整个程序。

限制正则表达式的长度是行不通的,因为问题在于回溯。例如,对不包含“x”的长度为N的字符串执行匹配正则表达式r"(\S+)+x",将进行2 ** N次回溯。在我的系统上,对"a"*21进行匹配大约需要一秒钟,每增加一个字符,时间就会加倍,因此长度为100个字符的字符串需要大约19167393131891000年才能完成(这是一个估计值,我没有计时)。

想了解更多信息,可以阅读O'Reilly图书《精通正则表达式》-其中有几章关于性能的内容。

编辑为了解决这个问题,我们编写了一个正则表达式分析函数,尝试捕获和拒绝一些更明显的退化情况,但不可能捕获所有情况。

我们还考虑过修补re模块,以在回溯次数过多时引发异常。这是可能的,但需要更改Python C源代码并重新编译,因此不具有可移植性。我们还提交了一个修补程序,可以在针对Python字符串进行匹配时释放GIL,但我认为它没有被核心接受(Python仅持有GIL,因为正则表达式可以针对可变缓冲区运行)。


1
我想我可以再生成一个进程,如果它运行时间太长就杀掉它? - Skeletron
生成和销毁将会起作用,但对于运行每个匹配而言会增加相当大的开销。是否接受这样的代价取决于您。 - Dave Kirby
使用信号怎么样?能否停止非常长的正则表达式?http://docs.python.org/library/signal.html - Bite code

6

对于普通用户来说,给他们一个子集语言会更简单。例如,在shell的globbing规则中使用fnmatch或者SQL LIKE条件规则。

将用户语言翻译成适当的正则表达式以在运行时执行。


4

编译正则表达式应该相对安全。虽然它所编译的内容不是严格的NFA(由于反向引用,所以不太干净),但仍应该比较容易编译。

至于性能特征,这是另一个完全不同的问题。即使是小型的正则表达式,由于回溯而具有指数级时间特性。最好定义某些功能的子集,只支持非常有限的表达式,并且自己进行翻译。

如果您真的想支持普通的正则表达式,您要么必须信任您的用户(有时是一种选择),要么限制所使用的空间和时间量。我相信所使用的空间仅由正则表达式的长度确定。

编辑:正如Dave所指出的那样,显然在正则表达式匹配期间保持全局解释器锁定,这将使设置超时更加困难。如果是这种情况,您唯一的选择就是在单独的进程中运行匹配以设置超时。虽然不完全理想,但是可行的。我完全忘记了multiprocessing。感兴趣的点是this section共享对象。如果您真的需要硬性约束,则单独的进程是此处的最佳选择。


1
使用单独的线程来实现超时并不起作用,因为Python在执行匹配时保持GIL - 请参见我的答案。即使您修补了re以释放GIL,您仍需要添加一些方法来终止运行正则表达式的线程 - 这并不容易! - Dave Kirby
我的错误,那真的非常令人恼火。我会将我的回答编辑成更加含糊但有可能的内容。 - wds

1

除非需要重复使用许多不同的正则表达式,否则不必使用compile()。该模块已经缓存了最后的表达式。

如果允许用户输入任何正则表达式,则执行中的第2点可能非常困难。您可以使用很少的字符制作复杂的正则表达式,例如著名的(x+x+)+y。我认为这是一个尚未以一般方式解决的问题。一种解决方法是启动一个不同的线程并监视它,如果超过允许的时间,则终止该线程并返回错误。


0

我真的不认为仅通过将代码传递到re.compile中就可以执行代码。据我所知,re.compile(或任何语言中的任何正则表达式系统)将正则表达式字符串转换为有限自动机(DFA或NFA),尽管它具有“编译”的可怕名称,但与任何代码的执行无关。


0

在字符串上执行正则表达式操作时,技术上不需要使用re.compile()。实际上,如果您只执行一次操作,则编译方法通常会更慢,因为与初始编译相关的开销较大。

如果您担心“编译”这个词,那么完全可以避免它,直接将原始表达式传递给matchsearch等函数。这样做可能会稍微提高代码的性能。


1
我认为这有点离题了。要执行实际搜索,match 无论如何都必须进行编译步骤,这也是 OP 担心的事情。 - wds

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接