Heim > Artikel > Backend-Entwicklung > Schwachstelle beim Parsen der PHP-Kernelschicht durch Deserialisierung
Vorwort
Während ich PHP lernte, stellte ich fest, dass einige PHP-Funktionen schwer zu verstehen sind, wie z wie etwa PHP 00-Kürzung, MD5-Fehler, Deserialisierungsumgehung __wakeup
und mehr. Ich möchte mich nicht auf das oberflächliche Verständnis beschränken, sondern untersuchen, wie der PHP-Kern das macht.
Das Folgende ist eine Deserialisierungsschwachstelle, die häufig in CTF verwendet wird, CVE-2016-7124 (unter Umgehung der magischen Funktion __wakeup), als Beispiel, um den Prozess des Debuggens des PHP-Kernels zu teilen. Einschließlich des gesamten Teils von der Einrichtung der Kernel-Quellcode-Debugging-Umgebung über die Serialisierung und Deserialisierung des Kernel-Quellcodes bis hin zur endgültigen Schwachstellenanalyse. (Empfohlen: PHP-Tutorial)
1. Gedanken, die durch ein Beispiel ausgelöst werden
Wir können uns zunächst das kleine Beispiel ansehen, das ich geschrieben habe.
Basierend auf dem Bild oben stellen wir zunächst die magischen Funktionen in PHP vor:
Werfen wir zunächst einen Blick auf die offizielle Dokumentation für mehrere häufig verwendete magische Funktionen. Einführung:
Um es hier zusammenzufassen: __construct
wird aufgerufen, wenn eine Klasse als Instanz initialisiert wird, und __destruct
wird aufgerufen, wenn sie initialisiert wird wird zerstört.
Wenn eine Klasse serialize
zur Serialisierung aufruft, wird die Funktion __sleep
automatisch aufgerufen. Wenn eine Zeichenfolge mit unserialize
in eine Klasse deserialisiert werden soll, wird die Funktion __wakeup
aufgerufen . Die oben genannten magischen Funktionen werden automatisch aufgerufen, sofern sie vorhanden sind. Sie müssen keine manuellen Display-Aufrufe durchführen.
Schauen wir uns nun den ersten Teil des Codes an. In der Funktion __destruct
gibt es sensible Vorgänge zum Schreiben von Dateien. Wir verwenden hier Deserialisierung, um gefährliche Zeichenfolgen zu erstellen, die zu Schwachstellen bei der Codeausführung führen können.
Als wir die entsprechende Zeichenfolge erstellten und uns auf deren Verwendung vorbereiteten, stellten wir fest, dass in ihrer __wakeup
-Funktion ein Filtervorgang vorhanden war, der unsere Konstruktion behinderte. Weil wir wissen, dass die Deserialisierung zuerst die Funktion __wakeup
aufrufen muss.
Hier kommen wir nicht umhin, über die Verwendung dieser PHP-Deserialisierungsschwachstelle CVE-2016-7124 (Umgehung der magischen Funktion __wakeup) nachzudenken, um die magische Funktion ___wakeup, die während der Deserialisierung automatisch aufgerufen wird, einfach zu umgehen Operationen werden in die Datei geschrieben.
Natürlich ist der obige Code nur ein einfaches Beispiel, das ich persönlich gegeben habe, und in realen Situationen gibt es viele ähnliche Situationen wie oben. Aber diese Bypass-Methode fasziniert mich sehr. Wie wirkt sich der interne Betrieb und die Verarbeitung von PHP auf die Codelogik der oberen Ebene aus, um eine solche magische Situation (BUG) zu verursachen? Als nächstes werde ich eine dynamische Debugging-Analyse des PHP-Kernels durchführen. Erkunden Sie diese Frage.
Diese Schwachstelle (CVE-2016-7124) betrifft Versionen der PHP5-Serie vor 5.6.25 und der 7.x-Serie vor 7.0.10. Daher werden wir später zwei Versionen kompilieren: Eine ist Version 7.3.0, die von dieser Sicherheitslücke nicht betroffen ist, und die andere Version ist Version 5.6.10, in der die Sicherheitslücke besteht. Vergleichen Sie die beiden Versionen, um mehr über die Unterschiede zu erfahren.
2. Aufbau einer PHP-Quellcode-Debugging-Umgebung
Wir alle wissen, dass PHP in der Sprache C entwickelt wird, weil die Umgebung, die ich verwende, es ist WIN 10. Daher stellen wir hauptsächlich den Umgebungsaufbau unter Windows vor. Wir benötigen die folgenden Materialien:
PHP源码 PHP SDK工具包,用于构建PHP 调试所需要IDE
Der Quellcode kann auf GITHUB heruntergeladen werden, Link: https://github.com/php/php-src, Sie können die gewünschte Version zum Herunterladen auswählen.
Download-Adresse des PHPSDK-Toolkits: https://github.com/Microsoft/php-sdk-binary-tools Das von dieser Adresse heruntergeladene Toolkit unterstützt nur VC14 und VC15. Natürlich finden Sie auch VC11, VC12 usw., die niedrigere Versionen von PHP unterstützen, unter https://windows.php.net/downloads/. Bevor Sie das PHP SDK verwenden, müssen Sie sicherstellen, dass Sie VS mit den entsprechenden installiert haben Version der Windows SDK-Komponente.
Im folgenden Artikel werden PHP7.3.0 und 5.6.10 verwendet. Die Quellcodekompilierung dieser beiden Versionen wird im Folgenden vorgestellt. Die Methoden für andere Versionen sind ähnlich.
2.1 Kompilieren von Windows PHP 7.3.0
Native Umgebung WIN10 X64, PHP SDK wird über den obigen Github-Link heruntergeladen. Gehen Sie in das SDK-Verzeichnis und suchen Sie nach 4 Batchdateien. Doppelklicken Sie hier auf phpsdk-vc15-x64
.
Geben Sie dann phpsdk_buildtreephp7
in diese Shell ein. Sie werden feststellen, dass der PHP7-Ordner im selben Verzeichnis angezeigt wird und sich auch das Shell-Verzeichnis geändert hat.
Dann legen wir den dekomprimierten Quellcode unter php7vc15x64, Shell in diesem Ordner ab und verwenden den Befehl phpsdk_deps–update–branchmaster
, um die relevanten abhängigen Komponenten zu aktualisieren und herunterzuladen.
Nachdem Sie auf den Abschluss gewartet haben, geben Sie das Quellcodeverzeichnis ein und doppelklicken Sie auf die buildconf.bat
Batchdatei. Dadurch werden die beiden Dateien configure.bat
und configure.js
freigegeben. cli– in der Shell konfiguriert die entsprechenden Kompilierungsoptionen. Wenn Sie andere Anforderungen haben, können Sie configure –help ausführen, um
zu sehen Verwenden Sie nach den Eingabeaufforderungen nmake, um direkt zu kompilieren.
Die Kompilierung ist abgeschlossen und das Verzeichnis der ausführbaren Dateien befindet sich im Ordner php7vc15x64php-srcx64Debug_TS. Wir können php -v eingeben, um relevante Informationen anzuzeigen.
2.2 Kompilieren Sie Windows PHP 5.6.10
Die Methode ist die gleiche wie bei 7.3.0, beachten Sie jedoch, dass PHP5.6 verwendet Die WindowsSDK-Komponentenversion ist VC11. Sie müssen VS2012 herunterladen und können das von Github heruntergeladene PHP SDK nicht zum Kompilieren verwenden. Sie müssen das VC11 PHP SDK und die zugehörigen abhängigen Komponenten auf https://windows.php.net/ auswählen. Downloads/ zum Kompilieren. Der Rest ist genau das gleiche wie oben und wird hier nicht wiederholt.
2.3 Debugging-Konfiguration
Da wir oben den PHP-Interpreter kompiliert haben, verwenden wir hier VSCODE direkt zum Debuggen.
Installieren Sie die C/C++-Debugging-Erweiterung, nachdem der Download abgeschlossen ist.
Öffnen Sie dann das Quellcodeverzeichnis, klicken Sie auf Debuggen -> Konfiguration öffnen, die Datei launch.json wird geöffnet.
Gemäß der Abbildung oben können Sie nach der Konfiguration dieser drei Parameter PHP-Code in 1.php im aktuellen Verzeichnis schreiben und Haltepunkte im PHP-Quellcode festlegen direktes Debuggen.
Die Debugging-Umgebung ist eingerichtet.
3. PHP-Deserialisierungs-Quellcode-Analyse
Im Allgemeinen gibt es bei der PHP-Deserialisierung normalerweise zwei Funktionen, Serialisierung und Deserialisierung, die natürlich paarweise auftreten nicht notwendig. Es gibt auch zwei magische Methoden __sleep() und __wakeup(). Wie wir alle wissen, bedeutet Serialisierung lediglich, dass Objekte in Dateien gespeichert werden, während Deserialisierung genau das Gegenteil bedeutet. Die Objekte werden aus der Datei ausgelesen und instanziiert.
Als nächstes verwenden wir basierend auf der oben eingerichteten Debugging-Umgebung dynamisches Debugging, um intuitiv darzustellen, was Serialisierung und Deserialisierung in PHP (Version 7.3.0) erfolgt.
3.1 Quellcodeanalyse serialisieren
Schreiben wir zunächst eine einfache Demo, die die __sleep
magische Funktion nicht enthält:
Dann suchen wir global nach der Funktion serialize
im Quellcode und finden diese Funktion in der Datei var.c. Wir setzen einen Haltepunkt direkt unter den Funktionsheader und beginnen mit dem Debuggen.
Wir können sehen, dass wir nach einigen Vorbereitungsarbeiten mit der Serialisierungsverarbeitungsfunktion beginnen und anschließend die Funktion php_var_serialize
ausführen.
Wir werden hier weiterhin der php_var_serialize_intern
-Funktion folgen. Da es viele Funktionscodes gibt, haben wir nur die ausgeschnitten Wichtige Teile hier. Die Funktion befindet sich immer noch in der var.c-Datei.
Die Struktur der gesamten Funktion ist ein Schalterfall, und der Typ der Strukturvariante wird durch das Makro Z_TYPE_P analysiert (dieses Makro wird zu struc-> erweitert). ;u1.v.type), um den zu serialisierenden Typ zu bestimmen, und geben Sie dann den entsprechenden CASE-Teil für den Betrieb ein. Die folgende Abbildung zeigt die Typdefinition.
Anhand der Nummer 8 im roten Feld oben wissen wir, dass es in ein Objekt serialisiert werden muss IS_OBJECT
und geben Sie den entsprechenden CASE-Zweig ein:
Wir sehen den Zeitpunkt des Aufrufs der magischen Funktion __sleep
im Bild oben. Da diese Funktion in der von uns geschriebenen Demo nicht vorhanden ist, wird der Prozess nicht in diesen Zweig eintreten. Verschiedene Zweige repräsentieren unterschiedliche Verarbeitungsabläufe. Wir werden uns den Prozess mit der magischen Funktion __sleep später ansehen.
Da es im obigen Fall IS_OBJECT-Zweig keinen Prozesstreffer gibt und es in diesem Fall keine Break-Anweisung gibt, wird die Ausführung mit dem IS_ARRAY-Zweig fortgesetzt, wo die Die Klasse wird aus dem Namen der Struc-Struktur extrahiert, ihre Länge berechnet und der Buf-Struktur zugewiesen. Anschließend wird die in der Klasse zu serialisierende Struktur extrahiert und im Hash-Array gespeichert.
Der nächste Schritt besteht darin, die Funktion php_var_serialize_intern
zu verwenden, um das gesamte Hash-Array rekursiv zu analysieren, den Variablennamen und -wert daraus zu extrahieren, eine Formatanalyse durchzuführen, und schließen Sie das Parsen ab. Die Zeichenfolge wird in die Buf-Struktur gespleißt. Wenn der gesamte Prozess abgeschlossen ist, wird die gesamte Zeichenfolge schließlich vollständig in der flexiblen Array-Struktur buf gespeichert.
Wie aus dem roten Kästchen im Bild oben ersichtlich ist, stimmt es mit dem Endergebnis überein. Als nächstes modifizieren wir die Demo leicht und fügen die magische Funktion __sleep
hinzu. Laut offizieller Dokumentation muss die Funktion __sleep
ein Array zurückgeben. Wir haben in dieser Funktion auch eine Klassenmitgliedsfunktion aufgerufen. Beobachten Sie sein spezifisches Verhalten.
Der vorherige Vorgang ist genau derselbe und wird hier nicht wiederholt. Beginnen wir am Verzweigungspunkt.
Wir folgen direkt der php_var_serialize_call_sleep
-Funktion.
Hier folgen wir weiter call_user_function
Laut Makrodefinition ruft es hier tatsächlich einige Kopieraktionen auf , werden keine Screenshots erstellt und der Prozess fährt mit dem Aufruf der Funktion _call_user_function_ex
fort. zend_call_function
, unter tatsächlichen Umständen müssen wir einige unserer eigenen Dinge in zend_call_function
tun, wobei PHP die auszuführenden Vorgänge vorantreibt in PHP In Ihrem eigenen __sleep
Engine-Stack wird es später einzeln analysiert (dh der entsprechende OPCODE wird analysiert). zend_vm
. zend_execute_ex
, um an die entsprechende Verarbeitungsfunktion zu senden. ZEND_FASTCALL
aufgerufen haben, suchen wir hier zuerst show, und dann werden wir die Die folgenden Operationen werden für die nächste Runde des neuen Parsens (hier werden die Operationen in der Show verarbeitet) weiterhin in den ZEND_VM-Stapel verschoben, bis die gesamte Operation analysiert ist. Wir werden hier nicht weiter darauf eingehen. __sleep
Nach einem so großen Kreis führen verschiedene Wege zum gleichen Ziel. Nach der Verarbeitung einer Reihe von Operationen in der Funktion _sleep wird die Funktion php_var_serialize_class verwendet, um den Klassennamen zu serialisieren und die Struktur im Rückgabewert ihres _sleep rekursiv zu serialisieren Funktion. Abschließend werden die Ergebnisse in der buf-Struktur gespeichert. An diesem Punkt ist der gesamte Serialisierungsprozess abgeschlossen.
3.1.1 Zusammenfassung des Serialisierungsprozesses
Fassen wir den Serialisierungsprozess zusammen:
Wenn es keine magische Funktion gibt, serialisieren Sie den Klassennamen –> ; Verwenden Sie Rekursion, um die verbleibende Struktur zu serialisieren.
Wenn es eine magische Funktion gibt, rufen Sie die magische Funktion __sleep auf–>Verwenden Sie die ZEND_VM-Engine, um PHP-Operationen zu analysieren –>Ein Array von Strukturen zurückgeben, die serialisiert werden müssen– >Serialisierungsklassenname –> Rekursive Serialisierung der Rückgabewertstruktur von __sleep nutzen.
3.2 Quellcodeanalyse deserialisieren
Nachdem wir den Serialisierungsprozess gelesen haben, schauen wir uns als Nächstes den unserialize
-Prozess aus der einfachsten Demo an. Dieses Beispiel enthält keine magischen Funktionen.
Die Methode ist die gleiche wie oben, unserialize
Der Quellcode befindet sich auch in der var.c-Datei.
Das obige Bild zeigt die neuen Funktionen in PHP7, gefilterte Deserialisierung, entsprechend allowed_classes
Stellen Sie die Situation auf Filter ein die entsprechenden PHP-Objekte, um eine illegale Dateninjektion zu verhindern. Die gefilterten Objekte werden in __PHP_Incomplete_Class对
-Objekte umgewandelt und können nicht direkt verwendet werden. Dies hat jedoch keine Auswirkungen auf den Deserialisierungsprozess und wird hier nicht im Detail erläutert. Wir folgen mit der Funktion php_var_unserialize.
Wir folgen hier weiterhin der php_var_unserialize_internal
Funktion.
Der wichtigste interne Vorgang dieser Funktion besteht darin, die Zeichenfolge zu analysieren und dann zum entsprechenden Verarbeitungsprozess zu springen. Der erste Buchstabe 0 wird in der obigen Abbildung analysiert, was die Deserialisierung in ein Objekt darstellt.
Hier wird zunächst der Objektname analysiert und eine Tabellensuche durchgeführt, um zu bestätigen, dass das Objekt tatsächlich existiert.
Nach Abschluss der oben genannten Vorgänge haben wir unser eigenes neues Objekt basierend auf dem Objektnamen new erstellt und initialisiert, aber unser Deserialisierungsvorgang ist immer noch fehlgeschlagen. Fertig, folgen wir mit der object_common2
-Funktion hochfahren.
Hier sehen wir die Beurteilung und Erkennung magischer Funktionen, aber der aufrufende Teil ist nicht hier. Folgen wir weiterhin der Funktion „process_nested_data“.
Es scheint, dass diese Funktion eine WHILE-Schleife verwendet, um die verbleibenden Teile einzeln zu analysieren. ·Sie enthält zwei php_var_unserialize_internal-Funktionen. Der erste analysiert den Namen und der zweite analysiert den dem Namen entsprechenden Wert. Nachdem die Funktion „process_nested_data“ ausgeführt wurde, ist die Zeichenfolgenanalyse abgeschlossen, der Hauptinhalt des Deserialisierungsvorgangs ist abgeschlossen und der Prozess steht kurz vor dem Ende.
Schicht für Schicht zur ursprünglichen Funktion zurückkehren PHP_FUNCTION
Wir sehen, dass einige Abschlussarbeiten durchgeführt wurden, der angewendete Speicherplatz freigegeben und die Deserialisierung abgeschlossen ist. Unsere magische Funktion __wakeup
wird hier nicht aufgerufen. Um den Aufrufzeitpunkt von __wakeup
herauszufinden, modifizieren wir die Demo hier.
Eine neue Runde des Debuggens beginnt hier. Es wurde festgestellt, dass nach Abschluss der Serialisierung der Aufruf, den wir erwarten, im Release-Bereich PHP_VAR_UNSERIALIZE_DESTROY
angezeigt wird.
Erinnern Sie sich noch an das VAR_WAKEUP_FLAG-Flag, wenn __wakeup während des Deserialisierungsprozesses gefunden wird? Wenn Sie hier das bar_dtor_hash-Array durchlaufen und auf dieses Flag stoßen, wird der Aufruf von __wakeup offiziell gestartet Die eingeführte __sleep-Aufrufmethode ist genau dieselbe und wird hier nicht wiederholt. Zu diesem Zeitpunkt sind alle Deserialisierungsprozesse abgeschlossen.
3.2.1 Zusammenfassung des Serialisierungsprozesses
Aus dem oben Gesagten können wir ersehen, dass der Deserialisierungsprozess im Vergleich zum Serialisierungsprozess nicht vom Vorhandensein magischer Funktionen abhängt. Unterschiede im Prozess zu schaffen. Der Deserialisierungsprozess läuft wie folgt ab:
Holen Sie sich die deserialisierte Zeichenfolge –> Deserialisieren Sie sie entsprechend dem Typ –> Suchen Sie in der Tabelle nach der entsprechenden Deserialisierungsklasse –> –> new erstellt eine neue Instanz –> analysiert iterativ die magische Funktion __wakeup und gibt den Speicherplatz frei –> .
4. PHP-Deserialisierungsschwachstelle
Mit der oben genannten Quellcodegrundlage untersuchen wir nun die Schwachstelle CVE-2016-7124 (Umgehung der magischen Funktion __wakeup).
Daher gelten für die Schwachstelle bestimmte Versionsanforderungen. Wir verwenden eine andere oben kompilierte PHP-Version (5.6.10), um diese Schwachstelle zu reproduzieren und zu debuggen.
Zuerst reproduzieren wir die Schwachstelle:
Wir können hier sehen, dass die TEST-Klasse nur ein Element $a enthält, und wir desequenzieren es hier beim Ändern Der Wert, der die Anzahl der Elemente in der Elementzeichenfolge darstellt, löst diese Sicherheitslücke aus. Diese Klasse vermeidet den Aufruf der magischen Funktion __wakeup
.
Natürlich wurde beim Auslösen der Sicherheitslücke auch ein interessantes Phänomen entdeckt. Dies ist nicht die einzige auslösende Methode.
Die Deserialisierungsvorgänge, die den vier Nutzlasten im obigen Bild entsprechen, lösen diese Sicherheitslücke aus. Obwohl die folgenden vier Schwachstellen auslösen, gibt es einige geringfügige Unterschiede. Hier ändern wir den Code leicht:
Aus der obigen Abbildung können wir ersehen, dass in der deserialisierten Zeichenfolge, solange die Elemente in der Parsing-Klasse angezeigt werden, diese Sicherheitsanfälligkeit vorliegt wird immer dann ausgelöst, wenn ein Fehler auftritt. Das Ändern der internen Vorgänge des Klassenelements (z. B. Ändern der Zeichenfolgenlänge, des Klassenvariablentyps usw. in der Abbildung oben) führt jedoch dazu, dass die Zuweisung der Klassenmitgliedsvariablen fehlschlägt. Nur wenn die Anzahl der Klassenmitglieder geändert wird (größer als die ursprüngliche Anzahl der Mitglieder), kann der Erfolg der Zuweisung von Klassenmitgliedern garantiert werden.
Schauen wir uns das Problem durch Debuggen an:
Nach unserer Analyse des Deserialisierungsquellcodes im dritten Teil vermuten wir, dass es bei der endgültigen Analyse auftreten könnte Variables Problem. Hier gelangen wir direkt zum Debugger für dynamisches Debuggen:
Wir sehen, dass diese Version im Vergleich zum Quellcode der Version 7.3.0 über keine Filterparameter verfügt und wurde bestanden. Bei so vielen Iterationsversionen scheint der Verarbeitungsprozess für niedrigere Versionen jetzt relativ einfach zu sein. Die allgemeine harmonische Logik hat sich hier jedoch nicht geändert. Die gleiche Logik wird hier nicht wiederholt. Wir werden den Unterschied (object_common2-Funktion) direkt verfolgen, der der Code für die Verarbeitung von Mitgliedsvariablen in der Klasse ist 🎜>
Das obige ist der detaillierte Inhalt vonSchwachstelle beim Parsen der PHP-Kernelschicht durch Deserialisierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!