【问题标题】:Safety of Python 'eval' For List Deserialization用于列表反序列化的 Python 'eval' 的安全性
【发布时间】:2009-07-11 01:25:19
【问题描述】:

在这种情况下是否可能发生任何安全漏洞:

eval(repr(unsanitized_user_input), {"__builtins__": None}, {"True":True, "False":False})

unsanitized_user_input 是一个 str 对象。该字符串是用户生成的,可能很讨厌。假设我们的 Web 框架没有让我们失望,它是来自 Python 内置的真正诚实的 str 实例。

如果这很危险,我们可以对输入做任何事情以使其安全吗?

我们绝对想要执行字符串中包含的任何内容。

另见:

(我认为)对这个问题并不重要的更大背景是我们有成千上万个这样的:

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。

【问题讨论】:

  • "如果我提出更大的背景,这样做的原因会更有意义" 你能详细说明这个问题吗?目前,该命令似乎毫无意义——就像对unsanitized_user_input.. 什么都不做一样。
  • “我们有成千上万个这样的”这没有意义。为什么要以这种方式存储输入?出于存储目的而对字符串进行 repr() 处理是没有意义的。
  • 你为什么不用泡菜或者更简单的东西?
  • 如果要相信文档,pickle 会慢一个数量级以上并且不一定更安全
  • 我也很好奇为什么数据首先会被重复。

标签: python eval


【解决方案1】:

这确实很危险,最安全的选择是ast.literal_eval(参见标准库中的ast 模块)。您当然可以构建和更改 ast 以提供例如在评估结果 AST 之前评估变量等(当它归结为文字时)。

eval 的可能利用从它可以得到的任何对象开始(比如这里的True),然后通过 .__class_ 到它的类型对象,等等直到object,然后得到它的子类。 ..基本上它可以达到任何对象类型并破坏破坏。我可以更具体一点,但我不想在公共论坛上这样做(这个漏洞是众所周知的,但考虑到有多少人仍然忽略它,向想要成为脚本小子的人透露它可能会使事情变得更糟......只是避免@987654327 @ 在未经处理的用户输入上,从此过上幸福的生活!-)。

【讨论】:

    【解决方案2】:

    如果您可以毫无疑问地证明 unsanitized_user_input 是来自 Python 内置函数的 str 实例且没有被篡改,那么这始终是安全的。事实上,即使没有所有这些额外的参数,它也是安全的,因为对于所有此类字符串对象,eval(repr(astr)) = astr。你输入一个字符串,你得到一个字符串。你所做的只是逃避和逃避它。

    这一切都让我认为eval(repr(x)) 不是你想要的——除非有人给你一个看起来像字符串但不是的unsanitized_user_input 对象,否则不会执行任何代码,但这是不同的问题——除非你试图以最慢的方式复制一个字符串实例:D。

    【讨论】:

    • 完全正确;我绝对不希望执行字符串中的任何内容。如果我提出更大的背景,这样做的原因会更有意义,但我试图简化问题的场景。
    【解决方案3】:

    根据您描述的所有内容,从技术上讲,评估 repred 字符串是安全的,但是,无论如何我都会避免这样做,因为它会自找麻烦:

    • 可能存在一些奇怪的极端情况,即您假设只存储 repred 字符串(例如,进入存储的错误/不同路径不会立即 repr 成为代码注入漏洞,否则它可能是不可利用)

    • 即使现在一切正常,假设在某个时候可能会发生变化,未经处理的数据可能会被不知道 eval 代码的人存储在该字段中。

    • 您的代码可能会被重用(或更糟糕的是,复制+粘贴)到您没有考虑过的情况中。

    正如Alex Martelli 所指出的,在python2.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添加的外引号。)

    【讨论】:

      【解决方案4】:

      一般来说,您绝不应允许任何人发布代码。

      所谓的“付费专业程序员”很难编写真正有效的代码。

      接受来自匿名公众的代码——没有正式的 QA 的好处——是所有可能情况中最糟糕的。

      专业的程序员——没有好的、可靠的正式 QA——几乎可以对任何网站进行哈希处理。事实上,我正在对付费专业人士的一些令人难以置信的糟糕代码进行逆向工程。

      允许非专业人士(不受 QA 阻碍)发布代码的想法确实令人恐惧。

      【讨论】:

        【解决方案5】:
        repr([unsanitized_user_input_1,
              unsanitized_user_input_2,
              ...
        

        ...unsanitized_user_input 是一个 str 对象

        您不必序列化字符串以将它们存储在数据库中..

        如果这些都是字符串,正如您所提到的 - 为什么不能将字符串存储在 db.StringListProperty 中?

        嵌套条目可能有点复杂,但为什么会这样呢?当你不得不求助于 eval 从数据库中获取数据时,你可能做错了什么......

        您不能将每个 unsanitized_user_input_x 存储为它自己的 db.StringProperty 行,并按参考字段对它们进行分组吗?

        其中任何一个都可能不适用,因为我不知道您想要实现什么,但我的意思是 - 您能否以您不必依赖 @987654327 的方式构建数据@(并且还依赖它不是安全问题)?

        【讨论】:

        • 一个原因是字符串需要被压缩以适应 App Engine 的 1MB 实体限制,我认为单独压缩 1000 个字符串的开销可能比序列化它们要高得多,压缩它们在一起,并将它们放入一个blob中。节省的空间可能也会更少。但这是一个很好的观点......我肯定在寻找避免 eval 而又不会过多增加运行成本的方法。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-12-08
        • 1970-01-01
        • 2014-04-12
        • 2012-03-25
        • 2021-11-21
        • 2018-01-29
        • 2017-04-03
        相关资源
        最近更新 更多