簡體   English   中英

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

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM