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

2024-11-21 08:33:00
admin
原创
6
摘要:问题描述:我遇到了一个情况,其中的一些代码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

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用