使用python's eval() vs. ast.literal_eval()

我有一些代码的情况,其中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()是唯一安全的选项吗?

339128 次浏览

因此,eval(input(...)) (Python 3)将在用户输入到达eval时立即对其求值,无论随后对数据做什么处理。因此,这不安全,特别是当你eval用户输入。

使用ast.literal_eval


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

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

datamap = eval(input('Provide some data here: '))意味着你实际评估代码之前,你认为它是不安全的。一旦函数被调用,它就对代码求值。另见eval的危险

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

在需要eval时使用ast.literal_eval。通常不应该计算Python语句的字面值。

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

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

__import__('os').system('rm -rf /a-path-you-really-care-about')传递给ast.literal_eval()将引发一个错误,但是eval()将很高兴地删除你的文件。

因为看起来你只是让用户输入一个普通的字典,所以使用ast.literal_eval()。它安全地做你想做的事,仅此而已。

< >强eval: 这是非常强大的,但如果您接受来自不可信输入的字符串进行计算,则也是非常危险的。假设要计算的字符串是“os”。System ('rm -rf /')"? 它会真正开始删除你电脑上的所有文件。

ast.literal_eval: 安全地计算表达式节点或包含Python文字或容器显示的字符串。提供的字符串或节点只能由以下Python文字结构组成:字符串、字节、数字、元组、列表、字典、集、布尔值、None、字节和集 语法:< / >强

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]除了对象本身什么都没有。 现在我们实例化了所有的子类,这里我们的主要__abc0目标是从中找到一个名为n的类

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

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

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

我被ast.literal_eval()卡住了。我在IntelliJ IDEA调试器中尝试它,它一直在调试器输出上返回None

但后来当我把它的输出赋值给一个变量并在代码中打印出来时。它运行得很好。共享代码示例:

import ast
sample_string = '[{"id":"XYZ_GTTC_TYR", "name":"Suction"}]'
output_value = ast.literal_eval(sample_string)
print(output_value)

它是python 3.6版。

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

这是在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")