[英]Using 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()
是唯一安全的選擇嗎?
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 文字結構組成:字符串、字節、數字、元組、列表、字典、集合、布爾值、無、字節和集合。
句法:
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() 現在更嚴格。 不再允許任意數字的加減。 關聯
Python急於求值,因此eval(input(...))
(Python 3) 將在用戶輸入達到eval
立即對其eval
,而不管您之后對數據進行了哪些操作。 因此,這並不安全,尤其是當您eval
用戶輸入時。
使用ast.literal_eval
。
例如,在提示符下輸入以下內容可能對您非常不利:
__import__('os').system('rm -rf /a-path-you-really-care-about')
如果您只需要用戶提供的字典,可能更好的解決方案是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")
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.