Maison  >  Article  >  développement back-end  >  Couche du noyau PHP analysant la vulnérabilité de désérialisation

Couche du noyau PHP analysant la vulnérabilité de désérialisation

藏色散人
藏色散人avant
2019-04-02 12:00:003481parcourir

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Avant-propos

Dans le processus d'apprentissage de PHP, j'ai trouvé que certaines fonctionnalités de PHP sont difficiles à comprendre, comme comme dans la troncature PHP 00, les failles MD5, le contournement de la désérialisation __wakeup et plus encore. Je ne veux pas m'en tenir à une compréhension superficielle, mais je veux explorer comment le noyau PHP le fait.

Ce qui suit est une vulnérabilité de désérialisation couramment utilisée dans CTF, CVE-2016-7124 (contournant la fonction magique __wakeup), comme exemple pour partager le processus de débogage du noyau PHP. Y compris toute la partie depuis la création de l'environnement de débogage du code source du noyau, l'analyse du code source du noyau de sérialisation et de désérialisation jusqu'à l'analyse finale de la vulnérabilité. (Recommandé : Tutoriel PHP)

1. Réflexions déclenchées par un exemple

Nous pouvons d'abord regarder le petit exemple que j'ai écrit.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Sur la base de l'image ci-dessus, introduisons d'abord les fonctions magiques en PHP :

Jetons d'abord un coup d'œil à la documentation officielle de plusieurs fonctions magiques couramment utilisées Introduction :

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Pour résumer ici, __construct sera appelé lorsqu'une classe est initialisée en tant qu'instance, et __destruct sera appelé lorsqu'elle l'est détruit.

Lorsqu'une classe appelle serialize pour la sérialisation, la fonction __sleep est automatiquement appelée. Lorsqu'une chaîne doit être désérialisée dans une classe à l'aide de unserialize, la fonction __wakeup est appelée. . Les fonctions magiques ci-dessus seront appelées automatiquement si elles existent. Pas besoin de passer manuellement des appels sur écran vous-même.

Regardons maintenant la partie initiale du code. Dans la fonction __destruct, il y a des opérations sensibles d'écriture de fichiers. Nous utilisons ici la désérialisation pour construire des chaînes dangereuses, ce qui peut entraîner des vulnérabilités d'exécution de code.

Lorsque nous avons construit la chaîne correspondante et nous sommes préparés à l'utiliser, nous avons constaté qu'il y avait une opération de filtrage dans sa fonction __wakeup, ce qui a gêné notre construction. Car on sait que la désérialisation doit d'abord appeler la fonction __wakeup.

Ici, nous ne pouvons nous empêcher de penser à utiliser cette vulnérabilité de désérialisation PHP CVE-2016-7124 (contournant la fonction magique __wakeup) pour contourner facilement la fonction magique ___wakeup qui est automatiquement appelée lors de la désérialisation Sensitive. les opérations sont écrites dans le fichier.

Bien sûr, le code ci-dessus n'est qu'un exemple simple que j'ai personnellement donné, et il existe de nombreuses situations similaires à celles ci-dessus dans des situations réelles. Mais cette méthode de contournement m'intrigue beaucoup. Comment le fonctionnement et le traitement internes de PHP affectent-ils la logique du code de la couche supérieure pour provoquer une telle situation magique (BUG). Ensuite, j'effectuerai une analyse de débogage dynamique du noyau PHP. Explorez cette question.

Cette vulnérabilité (CVE-2016-7124) affecte les versions des séries PHP5 antérieures à 5.6.25 et les séries 7.x antérieures à 7.0.10. Nous compilerons donc deux versions plus tard : l'une est la version 7.3.0 qui n'est pas affectée par cette vulnérabilité, et l'autre version est la version 5.6.10 où la vulnérabilité existe. Comparez les deux versions pour en savoir plus sur les différences.

2. Construction de l'environnement de débogage du code source PHP

Nous savons tous que PHP est développé en langage C, car l'environnement que j'utilise est WIN 10. Par conséquent, nous introduisons principalement la construction de l'environnement sous Windows. Nous avons besoin du matériel suivant :

PHP源码
PHP SDK工具包,用于构建PHP
调试所需要IDE

Le code source peut être téléchargé sur GITHUB, lien : https://github.com/php/php-src, vous pouvez sélectionner la version requise à télécharger.

Adresse de téléchargement de la boîte à outils PHPSDK : https://github.com/Microsoft/php-sdk-binary-tools La boîte à outils téléchargée à partir de cette adresse ne prend en charge que VC14 et VC15. Bien sûr, vous pouvez également trouver VC11, VC12, etc. qui prennent en charge les versions inférieures de PHP sur https://windows.php.net/downloads/ Avant d'utiliser le SDK PHP, vous devez vous assurer que VS est installé avec le correspondant. version du composant SDK Windows.

PHP7.3.0 et 5.6.10 seront utilisés dans l'article suivant. La compilation du code source de ces deux versions sera présentée ci-dessous. Les méthodes pour les autres versions sont similaires.

2.1 Compiler Windows PHP 7.3.0

Environnement natif WIN10 X64, le SDK PHP est téléchargé à partir du lien github ci-dessus. Entrez dans le répertoire SDK et recherchez 4 fichiers batch phpsdk-vc15-x64 ici.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Entrez ensuite phpsdk_buildtreephp7 dans ce shell, vous constaterez que le dossier php7 apparaît dans le même répertoire, et le répertoire du shell a également changé.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Ensuite, nous plaçons le code source décompressé sous php7vc15x64, shell dans ce dossier, et utilisons la commande phpsdk_deps–update–branchmaster pour mettre à jour et télécharger les composants dépendants pertinents.

Après avoir attendu la fin, entrez dans le répertoire du code source et double-cliquez sur le fichier batch buildconf.bat Il libérera les deux fichiers configure.bat et configure.js. cli– dans le shell. Enable-debug–enable-phar configure les options de compilation correspondantes. Si vous avez d'autres besoins, vous pouvez exécuter configure –help pour afficher

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Selon. les invites, utilisez nmake pour compiler directement.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

La compilation est terminée et le répertoire du fichier exécutable se trouve dans le dossier php7vc15x64php-srcx64Debug_TS. Nous pouvons entrer php -v pour afficher les informations pertinentes.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

2.2 Compiler Windows PHP 5.6.10

La méthode est la même que celle de la version 7.3.0, notez simplement que PHP5.6 utilise La version du composant WindowsSDK est VC11, vous devez télécharger VS2012 et vous ne pouvez pas utiliser le SDK PHP téléchargé depuis github pour la compilation. Vous devez sélectionner le SDK PHP VC11 et les composants dépendants associés sur https://windows.php.net/. téléchargements/pour compilation Le reste C'est exactement la même chose que ci-dessus et ne sera pas répété ici.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

2.3 Configuration du débogage

Parce que nous avons compilé l'interpréteur PHP ci-dessus, nous utilisons VSCODE directement pour le débogage ici.

Installez l'extension de débogage C/C++ une fois le téléchargement terminé.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Ensuite, ouvrez le répertoire du code source, cliquez sur Debug -> Open Configuration, le fichier launch.json s'ouvrira.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Selon l'image ci-dessus, après avoir configuré ces trois paramètres, vous pouvez écrire du code PHP en 1.php dans le répertoire courant et définir des points d'arrêt dans le code source PHP pour débogage direct.

L'environnement de débogage est mis en place.

3. Analyse du code source de la désérialisation PHP

De manière générale, en matière de désérialisation PHP, il existe généralement deux fonctions, sérialiser et désérialiser, qui apparaissent par paires, qui sont bien sûr pas nécessaire. Il existe également deux méthodes magiques __sleep() et __wakeup(). Comme nous le savons tous, la sérialisation signifie simplement que les objets sont stockés dans des fichiers, tandis que la désérialisation est tout le contraire. Les objets sont lus à partir du fichier et instanciés.

Ensuite, sur la base de l'environnement de débogage configuré ci-dessus, nous utilisons le débogage dynamique pour refléter intuitivement ce que la sérialisation et la désérialisation sont effectuées en PHP (version 7.3.0).

3.1 sérialiser l'analyse du code source

Écrivons d'abord une démo simple qui ne contient pas la __sleep fonction magique :

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Ensuite, nous recherchons globalement la fonction serialize dans le code source et localisons cette fonction dans le fichier var.c. Nous plaçons un point d'arrêt directement sous l'en-tête de la fonction et commençons le débogage.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Nous pouvons voir qu'après avoir fait quelques travaux de préparation, nous commençons à entrer dans la fonction de traitement de sérialisation, et nous enchaînons avec la fonction php_var_serialize.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Nous continuerons à suivre la fonction php_var_serialize_intern ici. Ce qui suit est la fonction de traitement principale. Parce qu'il existe de nombreux codes de fonction, nous n'avons supprimé que les codes de fonction. éléments clés ici. La fonction est toujours dans le fichier var.c.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

La structure de la fonction entière est un cas de commutation, et le type de la variante struc est analysé via la macro Z_TYPE_P (cette macro se développe en struc-> ;u1.v.type) , pour déterminer le type à sérialiser, puis entrez la partie CASE correspondante pour l'opération. La figure ci-dessous montre la définition du type.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

D'après le chiffre 8 dans l'encadré rouge ci-dessus, on sait qu'il faut le sérialiser en un objet IS_OBJECT et saisir la branche CASE correspondante :

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Nous voyons le timing d'appel de la fonction magique __sleep dans l'image ci-dessus Parce que cette fonction n'existe pas dans la démo que nous avons écrite, le processus n'entrera pas dans cette branche. Différentes branches représentent différents flux de traitement. Nous examinerons le processus avec la fonction magique __sleep plus tard.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Comme il n'y a aucun processus atteint dans la branche IS_OBJECT du cas ci-dessus, et qu'il n'y a pas d'instruction break dans le cas, l'exécution continue vers la branche IS_ARRAY, où le La classe est extraite du nom de la structure struc, calcule sa longueur et l'attribue à la structure buf, extrait la structure à sérialiser dans la classe et la stocke dans le tableau de hachage.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

L'étape suivante consiste à utiliser la fonction php_var_serialize_intern pour analyser de manière récursive l'intégralité du tableau de hachage, en extraire le nom et la valeur de la variable, effectuer une analyse du format, et terminez l'analyse. La chaîne est fusionnée dans la structure buf. Enfin, lorsque l'ensemble du processus est terminé, la chaîne entière est entièrement stockée dans la structure de tableau flexible buf.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Comme le montre le cadre rouge sur l'image ci-dessus, cela est cohérent avec le résultat final. Ensuite, nous modifions légèrement la démo et ajoutons la fonction magique __sleep Selon la documentation officielle, la fonction __sleep doit renvoyer un tableau. Nous avons également appelé une fonction membre de classe dans cette fonction. Observez son comportement spécifique.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Le processus précédent est exactement le même et ne sera pas répété ici. Commençons par le point de branchement.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

On suit directement la fonction php_var_serialize_call_sleep.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Continuons ici call_user_function Selon la définition de la macro, elle appelle en fait la fonction _call_user_function_ex et effectue quelques actions de copie ici, donc non. des captures d'écran sont prises et le processus passe à l'appel de la fonction zend_call_function.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

fonction zend_call_function, dans des circonstances réelles, nous devons faire certaines de nos propres choses dans __sleep, où PHP poussera les opérations à effectuer en PHP Dans votre propre pile de moteurs zend_vm, il sera analysé un par un plus tard (c'est-à-dire que l'OPCODE correspondant sera analysé).

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Le processus ici atteindra cette branche, nous suivons la fonction zend_execute_ex.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Nous pouvons voir ici que dans ZEND_VM, le flux de traitement global est une boucle while(1), qui analyse en continu les opérations dans la pile ZEND_VM. Le moteur ZEND_VM dans la case rouge de l'image ci-dessus utilisera la méthode ZEND_FASTCALL pour envoyer à la fonction de traitement correspondante.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Parce que nous avons appelé la fonction membre show dans __sleep, show est positionné en premier, puis les opérations suivantes continuent à pousser dans la pile ZEND_VM pour le prochain cycle de nouvelle analyse (ici, les opérations du show sont traitées) jusqu'à ce que l'opération entière soit analysée. Nous ne reviendrons pas plus loin ici.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Vous souvenez-vous encore du paramètre sortant retval ci-dessus, qui est la valeur de retour de __sleep L'image ci-dessus montre le premier élément x du tableau renvoyé Bien sûr, vous. peut également vérifier directement dans la variable.

Après un si grand cercle, différents chemins mènent au même objectif. Après avoir traité une série d'opérations dans la fonction _sleep, la fonction php_var_serialize_class est utilisée pour sérialiser le nom de la classe et sérialiser récursivement la structure dans la valeur de retour de son _sleep. fonction. Enfin, les résultats sont stockés dans la structure buf. À ce stade, tout le processus de sérialisation est terminé.

3.1.1 Résumé du processus de sérialisation

Résumons le processus de sérialisation :

Quand il n'y a pas de fonction magique, sérialisez le nom de la classe –> ; Utilisez la récursivité pour sérialiser la structure restante

Lorsqu'il y a une fonction magique, appelez la fonction magique __sleep–>Utilisez le moteur ZEND_VM pour analyser les opérations PHP—>Renvoyer un tableau de structures qui doivent être sérialisées– >Nom de la classe de sérialisation -> Utiliser la sérialisation récursive de la structure de valeur de retour de __sleep.

3.2 Analyse du code source de désérialisation

Après avoir lu le processus de sérialisation, nous examinerons ensuite le processus unserialize de la démo la plus simple. Cet exemple ne contient pas de fonctions magiques.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

La méthode est la même que ci-dessus, unserializeLe code source est également dans le fichier var.c.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Couche du noyau PHP analysant la vulnérabilité de désérialisation

L'image ci-dessus concerne les nouvelles fonctionnalités de PHP7, la désérialisation filtrée, selon allowed_classes Définir la situation pour filtrer les objets PHP correspondants pour empêcher l’injection illégale de données. Les objets filtrés seront convertis en objets __PHP_Incomplete_Class对 et ne pourront pas être utilisés directement, mais cela n'a aucun impact sur le processus de désérialisation et ne sera pas abordé en détail ici. Nous enchaînons avec la fonction php_var_unserialize.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Nous continuons à suivre la fonction php_var_unserialize_internal ici.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Le principal processus de fonctionnement interne de cette fonction consiste à analyser la chaîne, puis à passer au processus de traitement correspondant. La première lettre 0 est analysée dans la figure ci-dessus, qui représente la désérialisation en un objet.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Ici, le nom de l'objet sera d'abord analysé et une opération de recherche dans la table sera effectuée pour confirmer que l'objet existe.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Après avoir terminé les opérations ci-dessus, nous avons créé notre propre nouvel objet basé sur le nom de l'objet new et l'avons initialisé, mais notre opération de désérialisation a toujours échoué. Terminé, suivons avec la fonction object_common2.

Ici, nous voyons le jugement et la détection des fonctions magiques, mais la partie appelante n'est pas là. Continuons à suivre la fonction process_nested_data.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Il semble que cette fonction utilise une boucle WHILE pour analyser imbriquéement les parties restantes. ·Elle contient deux fonctions php_var_unserialize_internal, le premier analysera le nom et le second analysera la valeur correspondant au nom. Une fois l'exécution de la fonction process_nested_data terminée, l'analyse de la chaîne est terminée, le contenu principal de l'opération de désérialisation est terminé et le processus est sur le point de se terminer.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Retour à la fonction d'origine PHP_FUNCTION couche par couche On voit qu'un travail de finition est effectué, l'espace appliqué est libéré et la désérialisation est terminée. Notre fonction magique __wakeup n'est pas appelée ici. Afin de connaître le timing d'appel de __wakeup, nous modifions la Démo ici.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Une nouvelle série de débogage commence ici. Il a été constaté qu'une fois la sérialisation terminée, l'appel que nous nous attendions à voir est apparu dans l'espace de publication PHP_VAR_UNSERIALIZE_DESTROY.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Vous souvenez-vous encore de l'indicateur VAR_WAKEUP_FLAG lorsque __wakeup est trouvé pendant le processus de désérialisation ? Ici, lors de la traversée du tableau bar_dtor_hash et de la rencontre de cet indicateur, l'appel à __wakeup est officiellement lancé. La méthode d'appel ultérieure est la même que la précédente. . La méthode d'appel __sleep introduite est exactement la même et ne sera pas répétée ici. À ce stade, tous les processus de désérialisation sont terminés.

3.2.1 Résumé du processus de sérialisation

Nous pouvons voir de ce qui précède que le processus de désérialisation ne dépend pas de l'apparition ou non de la fonction magique par rapport au processus de sérialisation .pour créer des différences dans le processus. Le processus de désérialisation est le suivant :

Obtenez la chaîne désérialisée –> Désérialisez en fonction du type –> Recherchez dans le tableau la classe de désérialisation correspondante –> –> new crée une nouvelle instance -> analyse la chaîne restante de manière itérative -> détermine s'il existe une fonction magique __wakeup et la marque ->

4. Vulnérabilité de désérialisation PHP

Avec la base de code source ci-dessus, explorons maintenant la vulnérabilité CVE-2016-7124 (contournement de la fonction magique __wakeup).

Par conséquent, la vulnérabilité a certaines exigences de version. Nous utilisons une autre version de PHP (5.6.10) compilée ci-dessus pour reproduire et déboguer cette vulnérabilité.

Nous reproduisons d'abord la vulnérabilité :

Couche du noyau PHP analysant la vulnérabilité de désérialisation

On voit ici que la classe TEST ne contient qu'un seul élément $a, et nous le déséquençons ici lors de la modification la valeur représentant le nombre d'éléments dans la chaîne d'éléments, cette vulnérabilité sera déclenchée. Cette classe évite l'appel de la fonction magique __wakeup.

Bien sûr, un phénomène intéressant a également été découvert lors du processus de déclenchement de la vulnérabilité. Ce n'est pas la seule méthode de déclenchement.

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Les opérations de désérialisation correspondant aux quatre charges utiles dans l'image ci-dessus déclencheront cette vulnérabilité. Bien que les quatre ci-dessous déclenchent des vulnérabilités, il existe quelques différences mineures. Ici, nous modifions légèrement le code :

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Nous pouvons voir sur la figure ci-dessus que dans la chaîne désérialisée, tant que les éléments de la classe d'analyse apparaissent Cette vulnérabilité sera déclenché chaque fois qu’une erreur se produit. Cependant, la modification des opérations internes de l'élément de classe (telles que la modification de la longueur de la chaîne, du type de variable de classe, etc. dans la figure ci-dessus) entraînera l'échec de l'affectation de la variable du membre de classe. Ce n'est que lorsque le nombre de membres de la classe est modifié (plus grand que le nombre initial de membres) que le succès de l'affectation des membres de la classe peut être garanti.

Examinons le problème via le débogage :

D'après notre analyse du code source de la désérialisation dans la troisième partie, nous devinons qu'il peut se produire dans la version finale analysée problème variable. Ici on passe directement au débogueur pour un débogage dynamique :

Couche du noyau PHP analysant la vulnérabilité de désérialisation

On voit que par rapport au code source de la version 7.3.0, cette version n'a pas de paramètres de filtre et a été adopté. Avec autant de versions d'itérations, le processus de traitement des versions inférieures semble désormais relativement simple. Cependant, la logique harmonique globale n'a pas changé.Nous suivons ici directement la fonction php_var_unserialize. La même logique ne sera pas répétée ici.Nous suivrons directement la différence (fonction object_common2), qui est le code de traitement des variables membres dans la classe <.>

Couche du noyau PHP analysant la vulnérabilité de désérialisation

Dans la fonction object_common2, il y a deux opérations principales, process_nested_data analyse itérativement les données de la classe et l'appel de la fonction magique __wakeup, et lorsque la fonction process_nested_data ne parvient pas à analyser, il renvoie directement une valeur de 0 , la fonction __wakeup suivante n'aura aucune chance d'être appelée.


Voici l'explication pour laquelle il existe plus d'une charge utile qui déclenche la vulnérabilité.


Couche du noyau PHP analysant la vulnérabilité de désérialisation

Lorsque seul le nombre de membres de la classe est modifié, la boucle while peut être complétée une fois, ce qui permet d'attribuer complètement les variables membres de notre classe. Lors de la modification des variables membres internes, l'appel de fonction pap_var_unserialize échoue, puis les fonctions zval_dtor et FREE_ZVAL sont appelées pour libérer l'espace clé (variable) actuel, provoquant l'échec de l'affectation de variable dans la classe.


Par contre, dans la version PHP7.3.0, il n'y a pas de processus d'appel ici, c'est juste une simple marque, et le timing de l'ensemble du processus d'appel de la fonction magique est déplacé au point où les données sont publiées. Cela évite ce problème de contournement. Cette vulnérabilité devrait être causée par une faille logique.

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