web-dev-qa-db-fra.com

Le pirate a utilisé le téléchargement d'images pour obtenir le code PHP sur mon site

Je travaille sur un site Web - en ce moment, il est aux premiers stades des tests, pas encore lancé et n'a que des données de test - Dieu merci.

Tout d'abord, un pirate informatique a trouvé le mot de passe pour se connecter aux pages d'administration des sites Web.*. Je pense qu'ils ont utilisé un enregistreur de frappe sur l'ordinateur d'un ami qui s'est connecté au site pour me donner des commentaires.

Deuxièmement, ils ont utilisé une boîte de téléchargement d'image pour télécharger un fichier PHP. J'ai mis une vérification stricte afin que seuls les fichiers .jpg et .png soient acceptés - tout le reste aurait dû être rejeté. est-il impossible de télécharger un fichier .jpg, puis de modifier l'extension une fois le fichier stocké?

Heureusement, je génère également de nouveaux noms de fichiers lorsqu'un fichier m'est envoyé, donc je ne pense pas qu'ils aient pu localiser le fichier pour exécuter le code.

Je n'arrive pas à comprendre comment le site Web laisse passer un fichier PHP. Quel est le problème avec ma sécurité? Le code de la fonction de validation est ci-dessous:

function ValidateChange_Logo(theForm)
{
   var regexp;
   if (theForm.FileUpload1.value == "")
   {
      alert("You have not chosen a new logo file, or your file is not supported.");
      theForm.FileUpload1.focus();
      return false;
   }
   var extension = theForm.FileUpload1.value.substr(theForm.FileUpload1.value.lastIndexOf('.'));
   if ((extension.toLowerCase() != ".jpg") &&
       (extension.toLowerCase() != ".png"))
   {
      alert("You have not chosen a new logo file, or your file is not supported.");
      theForm.FileUpload1.focus();
      return false;
   }
   return true;
}

Une fois que le fichier arrive sur le serveur, j'utilise le code suivant pour conserver l'extension et générer un nouveau nom aléatoire. C'est un peu compliqué, mais ça marche bien.

// Process and Retain File Extension
$fileExt = $_FILES[logo][name];
$reversed = strrev($fileExt);
$extension0 = substr($reversed, 0, 1);
$extension1 = substr($reversed, 1, 1);
$extension2 = substr($reversed, 2, 1);
$fileExtension = ".".$extension2.$extension1.$extension0;
$newName = Rand(1000000, 9999999) . $fileExtension;

Je viens de tester avec un nom tel que logo.php;.jpg et bien que l'image ne puisse pas être ouverte par le site Web, elle a correctement changé le nom en 123456.jpg. Pour ce qui est de logo.php/.jpg, Windows n'autorise pas un tel nom de fichier.


* Pages protégées sur le site Web qui permettent des fonctions simples: comme télécharger une image qui devient alors un nouveau logo pour le site Web. Les détails FTP sont complètement différents du mot de passe utilisé pour se connecter aux pages protégées du site Web. Tout comme les informations d'identification de la base de données et de cPanel. J'ai veillé à ce que les gens ne puissent même pas voir la structure des dossiers et des fichiers du site. Il n'y a littéralement aucun moyen de penser à renommer une extension .jpg ou .png en .php sur ce site si vous n'avez pas de détails FTP.

117
Williamz902

Validation côté client

Le code de validation que vous avez fourni est en JavaScript. Cela suggère que c'est du code que vous utilisez pour effectuer la validation sur le client.

La règle numéro un de la sécurisation des webapps est de ne jamais faire confiance au client . Le client est sous le contrôle total de l'utilisateur - ou dans ce cas, de l'attaquant. Vous ne pouvez pas être sûr que le code que vous envoyez au client est utilisé pour quoi que ce soit, et aucun bloc que vous mettez en place sur le client n'a de valeur de sécurité. La validation sur le client sert uniquement à fournir une expérience utilisateur fluide, et non à appliquer réellement des contraintes de sécurité.

Un attaquant pourrait simplement changer ce morceau de JavaScript dans son navigateur, ou désactiver complètement les scripts, ou tout simplement ne pas envoyer le fichier à partir d'un navigateur mais créer sa propre requête POST avec un outil comme curl).

Vous devez tout revalider sur le serveur. Cela signifie que votre PHP doit vérifier que les fichiers sont à droite tapez quelque chose que votre code actuel ne fait pas.

Comment le faire est un vaste problème qui, je pense, sort du cadre de votre question, mais cette question sont de bons endroits pour commencer à lire. Vous voudrez peut-être jetez un œil à votre .htaccess fichiers également.

Obtenir l'extension

Ce n'est peut-être pas un problème de sécurité, mais this est une meilleure façon de le faire:

$ext = pathinfo($filename, PATHINFO_EXTENSION);

Magic PHP

Lorsque je stocke des données à partir de zones d'édition, j'utilise les trois fonctions de PHP pour les nettoyer:

$cleanedName = strip_tags($_POST[name]); // Remove HTML tags
$cleanedName = htmlspecialchars($cleanedName); // Allow special chars, but store them safely. 
$cleanedName = mysqli_real_escape_string($connectionName, $cleanedName);

Ce n'est pas une bonne stratégie. Le fait que vous mentionniez cela dans le contexte de la validation des extensions de fichier me fait craindre que vous ne lanciez que quelques fonctions liées à la sécurité à votre entrée en espérant que cela résoudra le problème.

Par exemple, vous devez utiliser des instructions préparées et ne pas vous échapper pour vous protéger contre SQLi. L'échappement des balises HTML et des caractères spéciaux pour empêcher XSS doit être effectué après la récupération des données de la base de données, et la façon de procéder dépend de l'endroit où vous insérez ces données. Ainsi de suite.

Conclusion

Je ne dis pas que c'est méchant, mais vous semblez faire beaucoup d'erreurs ici. Je ne serais pas surpris s'il existe d'autres vulnérabilités. Si votre application gère des informations sensibles, je vous recommande vivement de laisser une personne ayant une expérience de la sécurité les consulter avant de les mettre en production.

313
Anders

Tout d'abord, si quelqu'un a exploité une fonction de téléchargement de fichiers, assurez-vous que vous vérifiez le fichier sur le client et = serveur. Contourner la protection JavaScript côté client est trivial.

Deuxièmement, assurez-vous de passer au travers et de mettre en œuvre ces idées de OWASP .

Cela devrait couvrir la plupart de vos problèmes. Assurez-vous également que vous n'avez pas d'autres défauts non corrigés sur votre serveur (par exemple, des plugins obsolètes, des serveurs exploitables, etc.)

36
thel3l

Il est super important de noter ici que PHP a été conçu pour être intégré dans d'autres contenus. Plus précisément, il a été conçu pour être intégré dans des pages HTML, complétant une page largement statique avec des bits mineurs de données mises à jour dynamiquement.

Et, tout simplement, ( peu importe ce qu'il est intégré dans . L'interpréteur PHP analysera tout type de fichier arbitraire et exécutera tout contenu PHP correctement marqué avec des balises de script).

Le problème historique que cela a causé est que, oui, les téléchargements d'images peuvent contenir PHP. Et si le serveur Web est prêt à filtrer ces fichiers via l'interpréteur PHP, alors ce code s'exécutera. Voir le fiasco du plug-in Wordpress "Tim Thumb").

La bonne solution consiste à s'assurer que votre serveur Web ne traite jamais, jamais le contenu non approuvé comme quelque chose qu'il doit exécuter via PHP. Tenter de filtrer l'entrée est une façon de procéder, mais comme vous l'avez constaté, limité et sujet aux erreurs.

Il est préférable de vérifier que l'emplacement et l'extension du fichier sont utilisés pour définir ce que PHP va traiter et pour séparer les téléchargements et autres contenus non approuvés dans des emplacements et des extensions qui ne seront pas traités. cela varie d'un serveur à l'autre et souffre du problème "faire fonctionner les choses signifie généralement que la sécurité est relâchée d'une manière que vous ne connaissez pas".)

26
gowenfawr

Vous pouvez désactiver PHP dans votre répertoire de téléchargement par .htaccess afin que votre serveur n'exécute aucun code PHP dans le répertoire et dans ses sous-répertoires).

Créez un fichier .htaccess dans votre répertoire de téléchargement avec cette règle:

php_flag engine off

Veuillez noter que la règle .htaccess ne fonctionnera que si votre PHP fonctionne sous mod_php mode.

Edit: Bien sûr, j'ai oublié de mentionner que .htaccess ne fonctionne que dans Apache.

23
medskill

Vous ne faites que vérifier l'extension, cela ne correspond pas nécessairement au type de fichier réel. Côté serveur, vous pouvez utiliser quelque chose comme exif_imagetype pour vérifier que le fichier est une image. utilisation du type d'image exif: http://php.net/manual/en/function.exif-imagetype.php

11
iainpb

Je suis totalement d'accord avec la réponse acceptée, mais je suggérerais de faire un peu plus que de simplement jouer avec le nom de fichier. Vous devez recompresser le fichier original/téléchargé avec PHP en utilisant Gd ou Imagick et utiliser la nouvelle image . vous détruisez tout code injecté (pour être honnête, 90% du temps, il existe des moyens de faire survivre le code à la compression, mais c'est beaucoup de travail).

En outre, vous pouvez utiliser des fichiers .htaccess pour empêcher le répertoire de téléchargement de fonctionner PHP code (je ne connais pas IIS et il y a probablement un équivalent .webconfig).

<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>

De cette façon, le fichier téléchargé ne sera exécuté que si "l'extension finale" est PHP, donc image.php.jpg ne sera pas exécuté.

Quelques ressources:

6
akman

Une partie possible d'une solution ici est de conserver l'image, mais aussi de faire une copie redimensionnée avec PHP de sorte que vous puissiez vous assurer d'obtenir une image et réduire la taille de l'image d'origine. Cela a plusieurs effets positifs:

  • Ne laisse jamais l'image d'origine être utilisée pour l'affichage, donc elle est plus sécurisée
  • Économise de l'espace sur votre serveur (ce qui peut ou non être un problème)
  • Meilleur temps de chargement pour les visiteurs du site car ils n'attendraient pas le chargement d'une image de 10 Mo. Au lieu de cela, ils ont une image de 300 Ko à un chargement de taille raisonnable

Le point principal est que vous ne pouvez jamais faire confiance aux données utilisateur. Validez tout, les téléchargements de fichiers, les entrées, les cookies pour vous assurer que les données ne peuvent pas causer de problèmes.

6
LostBoy

J'ai juste une petite chose à ajouter à cela qui n'a pas été touchée: utiliser la stratégie de liste blanche.

Comme d'autres l'ont déjà souligné, s'appuyer uniquement sur la validation côté client n'est certainement pas une bonne chose. Vous avez également utilisé une stratégie de liste noire, ce qui signifie que vous vérifiez les choses que vous savez/pensez être mauvaises et que vous autorisez tout le reste. Une stratégie beaucoup plus robuste consiste à vérifier les choses que vous savez correctes et à rejeter tout le reste.

Parfois, il s'agit d'un compromis avec les commentaires des utilisateurs, comme faire savoir aux utilisateurs inexpérimentés ce qu'ils doivent faire pour télécharger avec succès une image.

D'après mon expérience, les deux stratégies ne s'excluent pas nécessairement tant qu'elles sont utilisées à des fins différentes: validation de la liste noire côté client pour les commentaires des utilisateurs et validation de la liste blanche côté serveur pour la sécurité.

2
Daniel

J'autorise les téléchargements d'images mais je suppose que chacun est chargé avec du code de cheval de Troie. Je place temporairement l'image téléchargée au-dessus de la racine Web lors du téléchargement, puis utilise ImageMagick pour la convertir en BMP, puis en JPG, puis je la déplace là où l'application Web peut l'utiliser. Cela supprime efficacement tout code PHP PHP intégré.

Comme @Sneftel le souligne dans le commentaire ci-dessous, effectuer cette conversion sur un JPG entraînera une certaine dégradation de l'image. Pour moi, c'est un compromis acceptable.

2
a coder

Vérifiez les balises EXIF ​​d'une image ou utilisez une fonction strpos pour trouver un code PHP dans le contenu de l'image. Exemple:

$imageFile = file_get_contents($your_image_temp_path); // ATTENTION! Temporary path for images is really needed for security reasons!
$dangerousSyntax = ['<?', '<?php', '?>'];
$error = '';
foreach($dangerousSyntax as $value) {
  $find = strpos($value, $imageFile);
  if( $find !== true ) {
    unlink($your_image_temp_path);
    $error = 'We found a dangerous code in your image';
  }
}

if(!empty($error)) { 
  die($error);
}
1
Tomek Leszczynski

En plus de la réponse acceptée, je suggérerais d'utiliser la fonction PHP intégrée getImageSize pour obtenir le type MIME et assurez-vous qu'une PHP le fichier ne se cache pas sous une extension de fichier image.

0
Grey