Utiliser eval () de python contre ast.literal_eval ()?

176

J'ai une situation avec un code qui eval()est apparu comme une solution possible. Maintenant, je n'ai jamais eu à l'utiliser eval()auparavant, mais j'ai trouvé de nombreuses informations sur le danger potentiel qu'il peut causer. Cela dit, je me méfie beaucoup de son utilisation.

Ma situation est que j'ai une entrée donnée par un utilisateur:

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

datamapdoit être un dictionnaire. J'ai cherché et j'ai trouvé que cela eval()pouvait fonctionner. J'ai pensé que je pourrais être en mesure de vérifier le type d'entrée avant d'essayer d'utiliser les données et ce serait une précaution de sécurité viable.

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

J'ai lu les documents et je ne sais toujours pas si cela serait sûr ou non. Eval évalue-t-il les données dès leur saisie ou après l' datamapappel de la variable?

Le astmodule est-il .literal_eval()la seule option sûre?

Tijko
la source

Réponses:

190

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

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_evalchaque fois que vous en avez besoin eval. Vous ne devriez généralement pas évaluer les instructions Python littérales.

Volatilité
la source
20
Ce n'est pas un conseil correct à 100% car tous les opérateurs binaires (ou surchargés) échoueront. Par exemple. ast.literal_eval("1 & 1")lancera une erreur mais eval("1 & 1")ne le fera pas.
Daniel van Flymen
1
Juste curieux. Ne devrions-nous pas utiliser des analyseurs d'expressions ou quelque chose si nous attendons quelque chose comme "1 & 1"?
thelinuxer
@thelinuxer vous devriez toujours, oui; vous ne seriez tout simplement pas capable d'utiliser ast.literal_evalpour quelque chose comme ça (par exemple, vous pourriez implémenter un analyseur manuellement).
Volatilité
104

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

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

Passer __import__('os').system('rm -rf /a-path-you-really-care-about')à ast.literal_eval()entraînera une erreur, mais eval()effacera volontiers votre lecteur.

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

Mixeur
la source
prend également en charge les chaînes d'octets (octets de classe). Par exemple. b'Hello World '
XChikuX
52

eval: Ceci est très puissant, mais est également très dangereux si vous acceptez des chaînes à évaluer à partir d'une entrée non approuvée. Supposons que la chaîne évaluée soit "os.system ('rm -rf /')"? Il commencera vraiment à supprimer tous les fichiers de votre ordinateur.

ast.literal_eval: évalue 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, dictées, ensembles, booléens, Aucun, 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 que nous avons instancié toutes les sous - classes , ici notre enter code hereobjectif principal est de trouver une classe nommée n à partir de celle-ci.

Nous avons besoin d' codeobjecter et d' functionobjecter à partir de sous-classes instanciées. Il s'agit d'une autre manière d' CPythonaccéder aux sous-classes d'objets et d'attacher le système.

Depuis python 3.7, ast.literal_eval () est désormais plus stricte. L'addition et la soustraction de nombres arbitraires ne sont plus autorisées. lien

Kiran Kumar Kotari
la source
1
J'utilise python 2.7 et je viens de vérifier son bon fonctionnement sur python 3.x. Mon mauvais j'ai continué à l'essayer sur python 2.7
Mourya
3
ast.literal_eval("1+1")ne fonctionne pas en 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 pouvoir analyser une opération binaire.
Sesshu
Pouvez-vous expliquer votre KABOOMcode, s'il vous plaît? KABOOM
Je l'ai
3
@winklerrr KABOOMest bien expliqué ici: nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
Elijas
41

Python est impatient de eval(raw_input(...))procéder à son évaluation, il évaluera donc l'entrée de l'utilisateur dès qu'elle atteindra le eval, indépendamment de ce que vous faites des données par la suite. Par conséquent, ce n'est pas sûr , en particulier lorsque vous evalentrez par l'utilisateur.

Utilisez ast.literal_eval.


À titre d'exemple, entrer ceci à l'invite sera très, très mauvais pour vous:

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

Si tout ce dont vous avez besoin est un dictionnaire fourni par l'utilisateur, une meilleure solution est possible json.loads. La principale limitation est que json dicts nécessite des clés de chaîne. De plus, vous ne pouvez fournir que des données littérales, mais c'est également le cas literal_eval.

Chinasaur
la source
1

J'étais coincé avec ast.literal_eval(). Je l'ai essayé dans le débogueur IntelliJ IDEA, et il a continué à revenir Nonesur la sortie du débogueur.

Mais plus tard, quand j'ai assigné sa sortie à une variable et l'ai imprimée dans le code. Cela a bien fonctionné. Exemple de code de partage:

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

Sa version python 3.6.

M Haziq
la source