262 votes

Utilisation de la fonction eval() de Python et de ast.literal_eval()

J'ai une situation avec un certain code où eval() est apparu comme une solution possible. Maintenant, je n'ai jamais eu à utiliser eval() mais j'ai trouvé de nombreuses informations sur le danger potentiel qu'il peut représenter. Cela dit, je suis très prudent quant à son utilisation.

Ma situation est la suivante : un utilisateur saisit des données :

datamap = input('Provide some data here: ')

Donde datamap doit être un dictionnaire. J'ai fait des recherches et j'ai trouvé que eval() pourrait s'en sortir. J'ai pensé que je pourrais vérifier le type d'entrée avant d'essayer d'utiliser les données et que ce serait une précaution de sécurité viable.

datamap = eval(input('Provide some data here: ')
if not isinstance(datamap, dict):
    return

J'ai lu la documentation et je ne sais toujours pas si cela est sûr ou non. Est-ce que l'évaluation évalue les données dès qu'elles sont saisies ou après l'exécution de l'opération ? datamap variable est appelée ?

Est-ce que le ast du module .literal_eval() la seule option sûre ?

269voto

Volatility Points 12668

datamap = eval(input('Provide some data here: ')) signifie que vous évaluez réellement le code antes de que vous le jugez dangereux ou non. Il évalue le code dès que la fonction est appelée. Voir aussi les dangers de eval .

ast.literal_eval lève une exception si l'entrée n'est pas un type de données Python valide, donc le code ne sera pas exécuté si ce n'est pas le cas.

Utilisez ast.literal_eval à chaque fois que vous en avez besoin eval . En général, vous ne devriez pas évaluer les instructions Python littérales.

34 votes

Ce n'est pas un conseil correct à 100 %, car tous les opérateurs bit à bit (ou opérateurs surchargés) échoueront. Par exemple ast.literal_eval("1 & 1") entraînera une erreur, mais eval("1 & 1") ne le fera pas.

1 votes

Simple curiosité. Ne devrions-nous pas utiliser des analyseurs d'expression ou autre chose si nous attendons quelque chose comme "1 & 1" ?

1 votes

@thelinuxer vous devriez quand même, oui ; vous ne pourriez juste pas utiliser ast.literal_eval pour quelque chose comme ça (par exemple, vous pourriez implémenter un parseur manuellement).

145voto

Blender Points 114729

ast.literal_eval() ne considère qu'un petit sous-ensemble de la syntaxe de Python comme valide :

La chaîne ou le nœud fourni ne peut être constitué que des structures littérales Python suivantes : chaînes de caractères, octets, nombres, tuples, listes, dicts, ensembles, booléens, et None .

Passing __import__('os').system('rm -rf /a-path-you-really-care-about') en ast.literal_eval() soulèvera une erreur, mais eval() supprimera volontiers vos fichiers.

Puisqu'il semble que vous ne laissiez l'utilisateur saisir qu'un simple dictionnaire, utilisez ast.literal_eval() . Il fait en toute sécurité ce que vous voulez et rien de plus.

81voto

eval : C'est très puissant, mais aussi très dangereux si vous acceptez d'évaluer des chaînes de caractères provenant d'une entrée non fiable. Supposons que la chaîne à évaluer soit "os.system('rm -rf /')" ? Elle commencera vraiment à effacer tous les fichiers de votre ordinateur.

ast.literal_eval : Évaluer en toute sécurité un nœud d'expression ou une chaîne contenant un littéral Python ou un affichage de conteneur. La chaîne ou le nœud fourni ne peut être constitué que des structures littérales Python suivantes : chaînes, octets, nombres, tuples, listes, dicts, ensembles, booléens, None, octets et ensembles.

Syntaxe :

eval(expression, globals=None, locals=None)
import ast
ast.literal_eval(node_or_string)

Exemple :

# 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__':{}})

Dans le code ci-dessus ().__class__.__bases__[0] rien d'autre que l'objet lui-même. Maintenant, nous avons instancié tous les Sous-classes ici notre principal enter code here L'objectif est de trouver une classe nommée n d'elle.

Nous devons code et function des sous-classes instanciées. Il s'agit d'une méthode alternative à CPython pour accéder aux sous-classes d'objets et attacher le système.

À partir de python 3.7, ast.literal_eval() est désormais plus strict. L'addition et la soustraction de nombres arbitraires ne sont plus autorisées. enlace

4 votes

ast.literal_eval("1+1") ne fonctionne pas dans python 3.7 et comme dit précédemment, literal_eval devrait être limité aux littéraux de ces quelques structures de données. Il ne devrait pas être capable d'analyser une opération binaire.

0 votes

Pouvez-vous expliquer votre KABOOM code, s'il vous plaît ? Je l'ai trouvé ici : KABOOM

3 votes

@winklerrr KABOOM est bien expliqué ici : nedbatchelder.com/blog/201206/eval_really_is_dangerous.html

53voto

nneonneo Points 56821

Python impatient dans son évaluation, donc eval(input(...)) (Python 3) évaluera l'entrée de l'utilisateur dès qu'elle atteindra l'option eval indépendamment de ce que vous faites des données par la suite. Par conséquent, ce n'est pas sûr surtout lorsque vous eval l'entrée de l'utilisateur.

Utilisez ast.literal_eval .


Par exemple, entrer ceci à l'invite pourrait être très mauvais pour vous :

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

11voto

Jason Morgan Points 864

Dans la version récente de Python3, ast.literal_eval() n'analyse plus les chaînes de caractères simples, mais vous êtes censé utiliser la méthode ast.parse() pour créer un AST et l'interpréter.

Voici un exemple complet de l'utilisation correcte de ast.parse() en Python 3.6+ pour évaluer des expressions arithmétiques simples en toute sécurité.

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")

0 votes

Et si je veux analyser un ast.Lambda, disons safe_eval("lambda x : x * 2") ? Merci beaucoup.

0 votes

L'article traite spécifiquement de l'évaluation arithmétique simple sans analyse du code, et non de l'analyse de la syntaxe Python. Si je peux faire "lambda x : x * 2". Alors je peux éventuellement faire "lambda x : format_hdd()". Quoi qu'il en soit, pour répondre à votre question, lorsque X est une variable, utilisez safe_eval("X * 2".replace("X", "55")). Dans mon application réelle, j'utilise une syntaxe similaire à celle des chaînes f, par exemple safe_eval(f"{X} * 2").

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X