使用 python 的 eval() 与 ast.literal_eval()
- 2024-11-21 08:33:00
- admin 原创
- 5
问题描述:
我遇到了一个情况,其中的一些代码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
。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件