我在多个地方多次看到这种情况,但从来没有找到令人满意的解释来解释为什么会出现这种情况。
所以,希望这里能介绍一个。为什么我们(至少,通常)不应该使用exec()
and eval()
?
编辑:我看到人们假设这个问题与网络服务器有关——事实并非如此。我可以看到为什么传递给的未经处理的字符串exec
可能是坏的。在非 Web 应用程序中是不是很糟糕?
我在多个地方多次看到这种情况,但从来没有找到令人满意的解释来解释为什么会出现这种情况。
所以,希望这里能介绍一个。为什么我们(至少,通常)不应该使用exec()
and eval()
?
编辑:我看到人们假设这个问题与网络服务器有关——事实并非如此。我可以看到为什么传递给的未经处理的字符串exec
可能是坏的。在非 Web 应用程序中是不是很糟糕?
通常有更清晰、更直接的方法来获得相同的效果。如果您构建一个复杂的字符串并将其传递给 exec,则代码难以遵循,也难以测试。
示例:我编写了读取字符串键和值并在对象中设置相应字段的代码。它看起来像这样:
for key, val in values:
fieldName = valueToFieldName[key]
fieldType = fieldNameToType[fieldName]
if fieldType is int:
s = 'object.%s = int(%s)' % (fieldName, fieldType)
#Many clauses like this...
exec(s)
对于简单的情况,该代码并不算太糟糕,但随着新类型的出现,它变得越来越复杂。当有错误时,它们总是在调用 exec 时触发,所以堆栈跟踪并不能帮助我找到它们。最终,我切换到了一个稍微长一点、不太聪明的版本,它明确地设置了每个字段。
代码清晰的第一条规则是代码的每一行都应该很容易理解,只需要查看它附近的行。这就是不鼓励使用 goto 和全局变量的原因。exec 和 eval 很容易破坏这条规则。
当您需要 exec 和 eval 时,是的,您确实需要它们。
但是,这些函数(以及其他脚本语言中的类似结构)的大部分野外使用是完全不合适的,可以用更快、更安全且错误更少的其他更简单的结构来代替。
通过适当的转义和过滤,您可以安全地使用 exec 和 eval。但是那种直接使用 exec/eval 来解决问题的编码器(因为他们不理解该语言提供的其他工具)不是那种能够正确处理的编码器。这将是一个不了解字符串处理并且只是盲目地连接子字符串的人,从而导致脆弱的不安全代码。
这是弦乐的诱惑。到处乱扔字符串段看起来很容易,并且使天真的编码人员误以为他们了解自己在做什么。但经验表明,在某些角落(或不那么角落)的情况下,结果几乎总是错误的,通常会带来潜在的安全隐患。这就是为什么我们说 eval 是邪恶的。这就是为什么我们说 regex-for-HTML 是邪恶的。这就是我们推动 SQL 参数化的原因。是的,您可以通过手动字符串处理来正确处理所有这些事情......但除非您已经理解我们为什么要说这些事情,否则您很可能不会。
eval() 和 exec() 可以促进惰性编程。更重要的是,它表明正在执行的代码可能没有在设计时编写,因此没有经过测试。换句话说,你如何测试动态生成的代码?尤其是跨浏览器。
抛开安全性不谈,eval
并且exec
由于它们引起的复杂性而经常被标记为不可取的。当你看到一个eval
调用时,你通常不知道它背后到底发生了什么,因为它作用于通常在变量中的数据。这使得代码更难阅读。
调用解释器的全部功能是一种重武器,只应保留在非常棘手的情况下。然而,在大多数情况下,最好避免使用它,而应该使用更简单的工具。
也就是说,就像所有的概括一样,要警惕这一点。在某些情况下, exec 和 eval 可能很有价值。但是你必须有一个很好的理由来使用它们。请参阅此帖子以了解一种可接受的用途。
与大多数答案在这里所说的相反, exec 实际上是在 Python 中构建超完整装饰器的秘诀的一部分,因为您可以准确地复制有关装饰函数的所有内容,为文档等目的生成相同的签名。它是广泛使用的装饰器模块(http://pypi.python.org/pypi/decorator/)功能的关键。exec/eval 必不可少的其他情况是在构建任何类型的“解释型 Python”应用程序时,例如 Python 解析的模板语言(如 Mako 或 Jinja)。
因此,这些功能的存在并不是“不安全”应用程序或库的直接标志。以天真的 javascripty 方式使用它们来评估传入的 JSON 或其他东西,是的,这是非常不安全的。但与往常一样,这一切都取决于您使用它的方式,这些都是非常重要的功能。
我过去曾使用eval()
过(并且仍然不时使用)在快速和肮脏的操作期间按摩数据。它是可用于完成工作的工具包的一部分,但由于其他答案中提到的所有原因,切勿将其用于您计划在生产中使用的任何东西,例如任何命令行工具或脚本。
你不能相信你的用户——永远——做正确的事。在大多数情况下,他们会这样做,但是您必须期望他们完成您从未想过的所有事情并找到您从未预料到的所有错误。这正是eval()
从工具到责任的地方。
一个完美的例子是在构建QuerySet
. 传递给查询的参数接受关键字参数,如下所示:
results = Foo.objects.filter(whatever__contains='pizza')
如果您以编程方式分配参数,您可能会考虑执行以下操作:
results = eval("Foo.objects.filter(%s__%s=%s)" % (field, matcher, value))
但总有一种不使用的更好方法,eval()
即通过引用传递字典:
results = Foo.objects.filter( **{'%s__%s' % (field, matcher): value} )
通过这种方式,它不仅在性能方面更快,而且更安全,更 Pythonic。
故事的道德启示?
小任务、测试和真正临时的事情可以使用,但不适合eval()
永久使用,因为几乎可以肯定总是有更好的方法来做到这一点!
在可能运行用户输入的上下文中允许这些功能是一个安全问题,并且实际工作的消毒剂很难编写。
与您不应该以 root 身份登录的原因相同:这太容易自取其辱了。
s = "import shutil; shutil.rmtree('/nonexisting')"
eval(s)
例如,现在假设有人可以从 Web 应用程序控制 s。
原因#1:一个安全漏洞(即编程错误......我们不能声称这些是可以避免的)并且您刚刚授予用户访问服务器外壳的权限。
在交互式解释器中试试这个,看看会发生什么:
>>> import sys
>>> eval('{"name" : %s}' % ("sys.exit(1)"))
当然,这是一个极端情况,但要防止这样的事情可能会很棘手。