使用python的eval()与ast.literal_eval()?

tij*_*jko 154 python eval abstract-syntax-tree

我有一些代码的情况,eval()作为一种可能的解决方案.现在我从来没有使用eval()过,但是,我已经发现了很多关于它可能造成的潜在危险的信息.也就是说,我对使用它非常谨慎.

我的情况是我有一个用户给出的输入:

datamap = raw_input('Provide some data here: ')
Run Code Online (Sandbox Code Playgroud)

哪里datamap需要是字典.我四处搜索,发现eval()可以解决这个问题.我认为在尝试使用数据之前我可以检查输入的类型,这将是一个可行的安全预防措施.

datamap = eval(raw_input('Provide some data here: ')
if not isinstance(datamap, dict):
    return
Run Code Online (Sandbox Code Playgroud)

我仔细阅读了文档,但我仍然不清楚这是否安全.eval在输入或datamap调用变量后立即评估数据吗?

ast模块是.literal_eval()唯一安全的选择吗?

Vol*_*ity 164

datamap = eval(raw_input('Provide some data here: '))意味着您认为代码不安全之前实际评估代码.它会在调用函数后立即评估代码.另见危险eval.

ast.literal_eval 如果输入不是有效的Python数据类型,则引发异常,因此如果不是,则不会执行代码.

使用ast.literal_eval时,你需要eval.您通常不应该评估文字Python语句.

  • 这不是100%正确的建议,因为任何按位运算符(或重载运算符)都将失败.例如.`ast.literal_eval("1&1")`会抛出一个错误,但`eval("1&1")`不会. (17认同)
  • @DanielvanFlymen - 对我来说,你的例子表明这*是*好的建议。当您不希望对运算符(如“&”)求值时,可以使用“literal_eval”。事实上,您不能在那里执行任意代码,这是一个功能,而不是一个错误。 (17认同)

Ble*_*der 95

ast.literal_eval() 只考虑Python的一小部分语法有效:

提供的字符串或节点可能只包含以下Python文字结构:字符串,数字,元组,列表,dicts,布尔值和None.

传递__import__('os').system('rm -rf /a-path-you-really-care-about')ast.literal_eval()将引发一个错误,但eval()会愉快地擦拭您的驱动器.

由于看起来您只是让用户输入普通字典,请使用ast.literal_eval().它安全地做你想要的,仅此而已.


Kir*_*ari 45

eval: 这是非常强大的,但如果您接受要从不受信任的输入进行评估的字符串,这也非常危险.假设被评估的字符串是"os.system('rm -rf /')"?它将真正开始删除您计算机上的所有文件.

ast.literal_eval: 安全地评估表达式节点或包含Python文字或容器显示的字符串.提供的字符串或节点可能只包含以下Python文字结构:字符串,字节,数字,元组,列表,字符串,集合,布尔值,无,字节和集合.

句法:

eval(expression, globals=None, locals=None)
import ast
ast.literal_eval(node_or_string)
Run Code Online (Sandbox Code Playgroud)

例:

# python 2.x - doesn't accept operators in string format
import ast
ast.literal_eval('[1, 2, 3]')  # output: [1, 2, 3]
ast.literal_eval('1+1') # output: ValueError: malformed string


# python 3.0 -3.6
import ast
ast.literal_eval("1+1") # output : 2
ast.literal_eval("{'a': 2, 'b': 3, 3:'xyz'}") # output : {'a': 2, 'b': 3, 3:'xyz'}
# type dictionary
ast.literal_eval("",{}) # output : Syntax Error required only one parameter
ast.literal_eval("__import__('os').system('rm -rf /')") # output : error

eval("__import__('os').system('rm -rf /')") 
# output : start deleting all the files on your computer.
# restricting using global and local variables
eval("__import__('os').system('rm -rf /')",{'__builtins__':{}},{})
# output : Error due to blocked imports by passing  '__builtins__':{} in global

# But still eval is not safe. we can access and break the code as given below
s = """
(lambda fc=(
lambda n: [
    c for c in 
        ().__class__.__bases__[0].__subclasses__() 
        if c.__name__ == n
    ][0]
):
fc("function")(
    fc("code")(
        0,0,0,0,"KABOOM",(),(),(),"","",0,""
    ),{}
)()
)()
"""
eval(s, {'__builtins__':{}})
Run Code Online (Sandbox Code Playgroud)

在上面的代码中().__class__.__bases__[0]只有对象本身.现在我们实例化了所有子类,这里我们的主要enter code here目标是从中找到一个名为n的类.

我们需要从实例化的子类中code对象和function对象.这是CPython访问对象的子类并附加系统的另一种方法.

来自python 3.7 ast.literal_eval()现在更加严格.不再允许添加和减去任意数字.链接

  • `ast.literal_eval("1+1")` 在 python 3.7 中不起作用,如前所述,literal_eval 应该仅限于那些少数数据结构的文字。它不应该能够解析二进制操作。 (3认同)
  • @winklerrr `KABOOM` 在这里得到了很好的解释:https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html (3认同)

nne*_*neo 41

Python 在评估时非常渴望,因此无论您之后如何处理数据,eval(raw_input(...))都会在用户输入时立即对其进行评估eval.因此,这不安全,尤其是当您进行eval用户输入时.

使用ast.literal_eval.


例如,在提示符处输入此信息对您来说非常非常糟糕:

__import__('os').system('rm -rf /a-path-you-really-care-about')
Run Code Online (Sandbox Code Playgroud)


Jas*_*gan 14

在最近的Python3中,ast.literal_eval()“不再解析简单的字符串”*,相反,你应该使用ast.parse()方法创建一个AST然后解释它。


* 更新(2023 年第 1 季度):我有时会收到有关此上下文中“简单字符串”含义的评论。在阅读当前状态后,我添加了此更新以尝试解决该问题。

我前段时间写了这个答案,当时我使用了参考资料中的“简单字符串”短语,遗憾的是我不记得来源了,但它可能已经过时了,但确实,这个方法一度期望得到一些东西除了字符串之外。所以当时这是对 Python 2 的引用,这一事实在 Python 3 中略有变化,但它确实有局限性。然后在某个时候我将 Py2 语法更新为 Py3 语法,导致混乱。

我希望这个答案仍然是一个完整的示例,说明如何编写一个安全的解析器,该解析器可以在作者的控制下评估任意表达式,然后可以通过清理每个参数来解释不受控制的数据。感谢评论,因为我仍然在实际项目中使用类似的东西!

所以真正唯一的更新是,ast.iteral_eval(str: statements)如果我理解正确的话,对于非常简单的 Python 表达式来说,现在被认为是安全的。

我希望这个答案仍然是一个最小的工作示例,说明如何实现类似的ast.literal_eval(str: statements)功能、方法和数据类型的多样性,但仍然以一种可以被认为是安全的简单方式。我确信还有其他方法,但这会脱离上下文,因为与这个问题的主题无关。


下面是在 Python 3.6+ 中正确使用 ast.parse() 来安全地计算简单算术表达式的完整示例。

import ast, operator, math
import logging

logger = logging.getLogger(__file__)

def safe_eval(s):

    def checkmath(x, *args):
        if x not in [x for x in dir(math) if not "__" in x]:
            raise SyntaxError(f"Unknown func {x}()")
        fun = getattr(math, x)
        return fun(*args)

    binOps = {
        ast.Add: operator.add,
        ast.Sub: operator.sub,
        ast.Mult: operator.mul,
        ast.Div: operator.truediv,
        ast.Mod: operator.mod,
        ast.Pow: operator.pow,
        ast.Call: checkmath,
        ast.BinOp: ast.BinOp,
    }

    unOps = {
        ast.USub: operator.neg,
        ast.UAdd: operator.pos,
        ast.UnaryOp: ast.UnaryOp,
    }

    ops = tuple(binOps) + tuple(unOps)

    tree = ast.parse(s, mode='eval')

    def _eval(node):
        if isinstance(node, ast.Expression):
            logger.debug("Expr")
            return _eval(node.body)
        elif isinstance(node, ast.Str):
            logger.debug("Str")
            return node.s
        elif isinstance(node, ast.Num):
            logger.debug("Num")
            return node.value
        elif isinstance(node, ast.Constant):
            logger.info("Const")
            return node.value
        elif isinstance(node, ast.BinOp):
            logger.debug("BinOp")
            if isinstance(node.left, ops):
                left = _eval(node.left)
            else:
                left = node.left.value
            if isinstance(node.right, ops):
                right = _eval(node.right)
            else:
                right = node.right.value
            return binOps[type(node.op)](left, right)
        elif isinstance(node, ast.UnaryOp):
            logger.debug("UpOp")
            if isinstance(node.operand, ops):
                operand = _eval(node.operand)
            else:
                operand = node.operand.value
            return unOps[type(node.op)](operand)
        elif isinstance(node, ast.Call):
            args = [_eval(x) for x in node.args]
            r = checkmath(node.func.id, *args)
            return r
        else:
            raise SyntaxError(f"Bad syntax, {type(node)}")

    return _eval(tree)


if __name__ == "__main__":
    logger.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    logger.addHandler(ch)
    assert safe_eval("1+1") == 2
    assert safe_eval("1+-5") == -4
    assert safe_eval("-1") == -1
    assert safe_eval("-+1") == -1
    assert safe_eval("(100*10)+6") == 1006
    assert safe_eval("100*(10+6)") == 1600
    assert safe_eval("2**4") == 2**4
    assert safe_eval("sqrt(16)+1") == math.sqrt(16) + 1
    assert safe_eval("1.2345 * 10") == 1.2345 * 10

    print("Tests pass")
Run Code Online (Sandbox Code Playgroud)


Chi*_*aur 6

如果您只需要用户提供的字典,可能更好的解决方案是json.loads. 主要限制是 json dicts 需要字符串键。此外,您只能提供文字数据,但literal_eval.