Python 'eval' 在列表反序列化中的安全性
在这种情况下,有没有可能出现安全漏洞:
eval(repr(unsanitized_user_input), {"__builtins__": None}, {"True":True, "False":False})
这里的 unsanitized_user_input
是一个字符串对象。这个字符串是用户生成的,可能会很恶心。假设我们的网络框架没有出错,它确实是一个来自Python内置的真实字符串实例。
如果这很危险,我们能对输入做些什么来确保它安全呢?
我们绝对 不 想执行字符串里面的任何内容。
另外,看看这些链接:
更大的背景是(我认为)对问题不是特别重要的是,我们有成千上万个这样的:
repr([unsanitized_user_input_1,
unsanitized_user_input_2,
unsanitized_user_input_3,
unsanitized_user_input_4,
...])
在某些情况下是嵌套的:
repr([[unsanitized_user_input_1,
unsanitized_user_input_2],
[unsanitized_user_input_3,
unsanitized_user_input_4],
...])
这些字符串本身是通过 repr()
转换成字符串的,存储在持久化存储中,最后通过eval读回内存。
使用eval从持久化存储中反序列化字符串的速度比pickle和simplejson快得多。解释器是Python 2.5,所以json和ast不可用。不允许使用C模块,也不允许使用cPickle。
5 个回答
根据你描述的情况,技术上来说,使用 eval 来处理 repred 字符串是安全的,但我还是建议你不要这样做,因为这可能会引发麻烦:
可能会出现一些奇怪的边缘情况,比如你假设只有 repred 字符串被存储,但实际上可能存在一个错误或不同的存储路径,这样就可能导致代码注入漏洞,而在其他情况下这个漏洞是无法被利用的。
即使现在一切正常,未来的情况可能会变化,某些人可能会在不知情的情况下把未处理的数据存储到这个字段里。
你的代码可能会被重用(更糟糕的是,被复制粘贴)到你没有考虑到的地方。
正如 Alex Martelli 指出的,在 Python 2.6 及更高版本中,有一个叫 ast.literal_eval 的功能,它可以安全地处理字符串和其他简单的数据类型,比如元组。这可能是最安全和最完整的解决方案。
另外一个选择是使用 string-escape
编解码器。这个方法比 eval 快很多(根据 timeit 的测试,大约快 10 倍),而且在比 literal_eval 更早的版本中就可以使用,应该能满足你的需求:
>>> s = 'he\nllo\' wo"rld\0\x03\r\n\tabc'
>>> repr(s)[1:-1].decode('string-escape') == s
True
([1:-1] 是用来去掉 repr 添加的外部引号的。)
如果你能毫无疑问地证明 unsanitized_user_input
是一个来自Python内置的 str
实例,并且没有被修改过,那么这总是安全的。实际上,即使没有那些额外的参数,这也是安全的,因为对于所有这样的字符串对象,eval(repr(astr)) = astr
。你输入一个字符串,输出的也是字符串。你所做的只是对它进行了转义和反转义。
这让我觉得 eval(repr(x))
可能不是你想要的——除非有人给你一个看起来像字符串但实际上不是的 unsanitized_user_input
对象,否则不会执行任何代码,但这又是另一个问题——除非你是想用最慢的方式复制一个字符串实例 :D。
确实,这样做是有风险的,最安全的替代方法是使用 ast.literal_eval
(可以查看标准库中的 ast 模块)。当然,你也可以构建和修改一个 ast
,以便在评估结果的抽象语法树(AST)之前,处理变量等内容(当它只涉及字面量时)。
使用 eval
可能会被利用,任何它能接触到的对象(比如这里的 True
)都可以成为攻击的起点,然后通过 .__class_ 获取它的类型对象,依此类推,一直到 object
,再获取它的子类……基本上,它可以接触到任何对象类型,造成严重的破坏。我可以更详细地说明,但我不想在公开的论坛上讨论(这个漏洞是众所周知的,但考虑到仍然有很多人忽视它,向那些想学编程的小白透露可能会让事情变得更糟……所以,尽量避免在未经处理的用户输入上使用 eval
,这样你就能过上快乐的生活!-)