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

2024-11-21 08:33:00
admin
原创
207
摘要:问题描述:我遇到了一个情况,其中的一些代码eval()被认为是可能的解决方案。我以前从未使用过,eval()但是,我遇到了大量有关它可能造成的危险的信息。话虽如此,我对使用它非常谨慎。我的情况是,我有一个用户给出的输入:datamap = input('Provide some data here: ') 哪...

问题描述:

我遇到了一个情况,其中的一些代码eval()被认为是可能的解决方案。我以前从未使用过,eval()但是,我遇到了大量有关它可能造成的危险的信息。话虽如此,我对使用它非常谨慎。

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

datamap = input('Provide some data here: ')

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

datamap = eval(input('Provide some data here: ')
if not isinstance(datamap, dict):
    return

我读完了文档,但仍然不清楚这是否安全。eval 是在输入数据后立即评估数据,还是在datamap调用变量后评估数据?

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


解决方案 1:

datamap = eval(input('Provide some data here: '))意味着您在判断代码是否安全之前会先对其进行评估。它会在调用函数时立即对代码进行评估。另请参阅的危险eval

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

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

解决方案 2:

ast.literal_eval()仅认为 Python 语法的一小部分是有效的:

提供的字符串或节点只能由以下 Python 文字结构组成:字符串、字节、数字、元组、列表、字典、集合、布尔值和None

传递__import__('os').system('rm -rf /a-path-you-really-care-about')进去ast.literal_eval()会引发错误,但eval()会很乐意删除您的文件。

由于看起来您只让用户输入一个简单的字典,因此请使用ast.literal_eval()。它可以安全地完成您想要的操作,仅此而已。

解决方案 3:

eval:
这个非常强大,但如果你接受来自不受信任的输入的字符串进行评估,那么它也非常危险。假设被评估的字符串是“os.system('rm -rf /')”?它实际上会开始删除计算机上的所有文件。ast.literal_eval


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

语法:

eval(expression, globals=None, locals=None)
import ast
ast.literal_eval(node_or_string)

例子:

# 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__':{}})

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

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

从 python 3.7 开始,ast.literal_eval() 变得更加严格。不再允许对任意数字进行加减运算。链接

解决方案 4:

Python 的求值非常迅速eval(input(...)),因此(Python 3)会在用户输入到达时立即求值eval,而不管之后您对数据做了什么。因此,这并不安全,尤其是当您eval输入用户输入时。

使用ast.literal_eval


举个例子,在提示符下输入以下内容可能会对你非常不利:

__import__('os').system('rm -rf /a-path-you-really-care-about')

解决方案 5:

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


* 更新(Q1:2023):我有时会收到关于此上下文中“简单字符串”含义的评论。在阅读当前状态后,我添加了此更新以尝试解决该问题。
我之前写过这个答案,当时我使用了我参考文献中的“简单字符串”短语,遗憾的是我不记得来源了,但它可能已经过时了,但确实曾经这种方法需要除字符串之外的其他东西。所以当时这是对 Python 2 的引用,而这一事实在 Python 3 中略有改变,但它确实有局限性。然后在某个时候,我将呈现的代码从 Py2 语法更新为 Py3 语法,造成了混乱。

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

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

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


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

import ast
import logging
import math
import operator

logger = logging.getLogger(__name__)


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

    bin_ops = {
        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,
    }

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

    ops = tuple(bin_ops) + tuple(un_ops)

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

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

    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")

解决方案 6:

如果您需要的只是用户提供的字典,那么更好的解决方案可能是json.loads。主要限制是 JSON 字典(“对象”)需要字符串键。此外,您只能提供文字数据,但 也是如此ast.literal_eval

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用