La faille Upload

To the non-french speaker, note that you can translate the articles using the Google Trad widget situated at the bottom of all pages.


Les formulaires d’upload sont souvent sujet à d’énormes failles de sécurité. En effet, ces formulaires permettent aux utilisateurs d’uploader un fichier (généralement une image) directement sur le serveur cible. Or si le formulaire n’est pas sécurisé alors rien n’empêchera un utilisateur malveillant d’upload un fichier PHP par exemple. Dans cet article nous verrons donc comment exploiter cette faille et surtout comment sécuriser nos formulaires.

I/ Exploitation

Bien, avant tout il va nous falloir des fichiers de test que je vous invite à télécharger ici. Pour créer un formulaire d’upload en HTML il faut utiliser la balise input suivie de l’attribut :

 type="file"

miniamlcode

Et voici le résultat visuel :

minimalcodevis

Là nous avons le striiiiiiiiiiiiict minimum. Voici un formulaire plus complet :

formcomplet.png

Plusieurs choses ont été rajouté. En premier nous avons l’attribut enctype. Cet attribut définit comment les données du formulaire doivent être encodées lorsqu’elles sont envoyées au serveur. Il existe trois types d’encodage :

application/x-www-form-urlencoded est la valeur par défaut. Tous les caractères sont encodés, les espaces sont remplacés par des « + » et les caractères spéciaux sont convertis en valeur ASCII HEX (par exemple le caractère ‘ est encodé en %27).
multipart/form-data est utilisé dans le cas ou on utilise un formulaire d’upload (c’est pour ça que je l’ai utilisé dans notre code HTML). Aucun caractère n’est encodé.
text/plain : aucun caractère n’est encodé et les espaces sont remplacés par des « + ».

Ensuite nous avons ajouté un champ caché que l’on a nommé « MAX_FILE_SIZE » et dont la valeur vaut 1000000 octets (10Mo). Ce champ indique que le fichier à uploader ne doit pas peser plus de 10000000 octets. Pour finir j’ai juste ajouté un bouton qui nous permettra d’envoyer les données au fichier reception.php.

Le fichier réception.php va effectuer quelques vérification sur le fichier envoyé et en particulier son type ainsi que sa taille. Ensuite on déplacera le fichier uploader vers le répertoire /upload.

Voici les différentes informations que l’on peut récupérer concernant le fichier uploadé :

datasfromfileupload.png

Et le code final qui permet de traiter le fichier uploadé :

codereceptiontypemime.png

Faisons quelques tests. Si j’upload un fichier .png :

png.png

Ca passe. Si j’upload un fichier .txt :

fail.png
Nous avons une erreur. Un développeur peu soucieux de la sécurité pourrait se dire que  c’est bon : son application est sécurisée. Grave erreur eheh !

La suite de l’article va se décomposer en quatre parties. A chaque fois nous verrons une méthode de sécurisation de la faille upload et comment la bypass jusqu’à arriver à un upload sécurisé. Et pour commencer nous allons nous intéresser au MIME.

  • Type MIME :

Le type MIME, aussi connu comme étant le content-type, est un identifiant de format de données sur Internet. Un type MIME est composé d’au moins deux parties : un type et un sous-type. Par exemple un fichier .png aura pour type « image » et pour sous-type « png ». Bien évidemment il existe plusieurs type MIME dont vous trouverez une liste non exaustive ici 😉😉 !

Si on reprend le code de notre fichier reception.png on se rend compte que ce qui permet de faire la différence entre un fichier qui sera uploadé et un fichier qui ne le sera pas, eh bien c’est le type MIME. Oui mais voilà, le type MIME est une donnée qui est envoyée dans la requête et s’il y a un envoi de requête cela veut dire que l’on peut l’intercepter… Et la modifier 😎 !

Pour cela nous aurons besoin d’un proxy web (coucou Burp Suite). Du coup si on se met en écoute,  qu’on upload un fichier .php :

typemimepassepas.png

Et qu’on change le type MIME en image/png :

bypassmime.png

On arrive à upload notre backdoor !

uploadsuccess.png

On vient donc de se rendre compte qu’il ne faut pas se fier au type MIME du fichier ! Du coup peut être qu’on pourrait s’intéresser à l’extension du fichier ?

  • Blacklister les extensions dangereuses :

Voici un des codes PHP que l’on pourrait utiliser :

typemimeetextension.png

Ici, en plus de vérifier le type MIME, on vérifie aussi l’extension du fichier envoyé. Si l’extension est .html ou .php alors on bloque l’upload. Sauf que là encore il y a un problème… Bah ouais on bloque l’extension .php mais on ne bloque pas l’extension .php3 ni .php4 (ou encore phtml et pwhl). Du coup si on upload un fichier backdoor.php3 qui contient ce code :

<?php
phpinfo();
?>

Sans oublier de modifier le MIME type eh bien notre fichier sera uploadé et exécutable :

pwn.png

Certains pourraient me dire : « Bah ok, il suffit de rajouter toutes les extensions .php dans la blacklist ». Ce à quoi je vous répondrai que c’est vrai. Mais avant de lire cet article, est ce que vous saviez qu’il y avait plusieurs extensions .php ? Non ?  Eh bien moi non plus quand j’ai commencé ! Donc cette solution n’est en définitive pas suffisante en terme de sécurité.

A la place de vérifier que l’extension ne fait partie de la blacklist, on pourrait faire l’inverse : vérifier que l’extension fait bien partie d’une whitelist !

  • Double Extension Exploit :

Le code que l’on pourrait utiliser dans cette situation ressemblerait plus à mois à ça :

codedoublextension.png

A l’inverse de l’exemple précédent, ici on ne va accepter que les fichiers dont l’extension est png, jpeg, gif ou png. Encore une fois il y a un gros problème… En effet qu’est ce qui nous empêche d’uploader un fichier qui a deux extension ? Eh bien justement… Pas grand chose :

doubleextensionexploit.png

Vous noterez que notre fichier a à la fois l’extension php et png. Du coup il passe avec succès les tests du fichier reception.php et en plus de ça le code PHP est interprété !

toobad.png

ATTENTION : pour cette exploitation il a fallu que je modifie le fichier de configuration d’Apache2 afin de le rendre vulnérable. Sur un fichier de configuration par défaut vous n’aurez pas cette faille.

  • Autres vecteurs d’attaque :

Il existe deux autres vecteurs d’attaques. Le premier c’est le Null Byte qui est une conséquence directe du fait que le langage PHP a été écrit en C. Pour délimiter la fin d’une chaîne de caractère en C il est obligatoire d’ajouter un caractère spécial : \0. Cela a mené à une grosse faille de sécurité puisque tout ce qui était situé après le Null Byte dans du code PHP était tout simplement supprimé. Autrement dit, si on upload un fichier qui porte ce nom : backdoor.php%00.png eh bien la partie %00.png va tout simplement être supprimé. Et notre fichier sera uploadé.

Le second vecteur est celui des images commentées. Sur la plupart des outils de retouche photo vous avez la possibilité d’inclure des commentaires dans le fichier. Par exemple sur GIMP si vous allez dans l’onglet « Image » -> « Propriété de l’image » vous aurez la possibilité d’écrire un commentaire. En utilisant cette fonctionnalité vous serez en mesure d’écrire du code PHP directement dans une photo, de l’upload sur le serveur et d’exécuter le code.

II/ Sécurisation

Voici comment je sécuriserais un formulaire d’upload. Pour commencer il faudra créer une liste de type MIME que l’on accepte à l’upload.

typemimeacceptes.png

Ensuite je créerais un tableau associatif qui contient les extensions des différents type MIME acceptés :

associatif.png

Si le type MIME reçu est dans la whitelist alors j’utilise la fonction explode afin de casser le nom du fichier au niveau du/des « . » :

explode.png

La fonction explode retourne un tableau. L’élément tab[0] contient le nom du fichier tandis que les éléments tab[1], tab[2] etc … Contiennent les différentes extensions. Si le tableau est plus grand que 2 cela veut dire qu’on a probablement tenté une attaque par double extension. Donc on bloque l’upload.

Si le tableau ne contient que deux éléments alors je prends l’élément tab[0] que je concatène à son extension en me basant sur le tableau associatif précédemment déclaré :

concatenation.png

En faisant cette opération j’empêche l’utilisateur de modifier son type MIME et par la même occasion je force le fichier à avoir une extension valide.

Étant donné que la limite de taille est stipulée dans le formulaire HTML, il serait bien aussi de vérifier que la taille du fichier uploadé ne dépasse par les 10Mo. Pour cela on pourra se servir de la fonction getimagesize() et d’un if statement :

taille.png

Enfin on oublie pas de déplacer le fichier dans un répertoire introuvable/touchable. L’idéal étant de déplacer le fichier dans un répertoire qui n’est pas accessible depuis le web (il faudra donc cloisonner l’utilisateur au répertoire /html par exemple). et de renommer le fichier uploadé (par son hash par exemple).

Et voici le code final :

codefinalsecured.png

Voilà voilà ! Avec ça il me semble compliquer de pouvoir utiliser un formulaire d’upload à des fins frauduleuses. Si vous pensez avoir trouver une faille n’hésitez pas à m’en faire part 😛 !

5 commentaires

  1. Bonjour,

    Merci pour ce petit tutoriel 🙂
    J’ai une question à propos de la double extension. Je comprends bien l’interêt d’utiliser une double extension pour bypass la vérif de l’extension si cette dernière est mal faite (genre si on utilise la fonction explode avec le poitn comme délimiteur et qu’on prends ensuite la dernière partie). Par contre ce que je ne comprends pas c’est pourquoi mon fichier test.php.png qui va être upload sous ce nom du coup sera interprété ? (vu que l’extension c’est .png au final, pourquoi le code php est interprété ?)

    Aimé par 1 personne

    1. Plop 😀 !

      Si cette attaque est possible c’est parce que j’ai été obligé de modifier le fichier de configuration d’Apache pour qu’il accepte les doubles extensions. Nativement, un fichier test.php.png sera traité comme étant une image : un .png.

      Sur Apache, il existe des handlers. Ces handlers sont relatifs à une extension (ici .php) et à un module (ici l’interpréteur PHP) présent sur le serveur Apache. Ce qu’il s’est passé ici c’est que le serveur, quand il a reçu la requête, a vu que le fichier contenait deux extensions. Une extension .png qui lui indique que c’est une image et une extension .php. Du coup le serveur s’est dit « Ah tiens, un .png. Ca veut dire que le fichier est une image. Ah mais il y a aussi du .php et j’ai un handler qui dit que dès que je reçois un .php je dois l’envoyer à l’interpréteur PHP. Bon bah viens test.php.png, je te redirige vers l’interpréteur PHP ! »

      Voilà pourquoi le code PHP est quand même exécuté 😀 ! Bonne journée 🙂 !

      J'aime

  2. Grand meci pour ce tuto fort instructif..
    le 2 em exemple et le 4 em exemple font des erreur quand j envoie une image traditionnelle..
    je ne sais pas si je fait un erreur?
    je suis en local avec xampp j’ai tester avec php5.5 & php 7

    //////////////////////////////////////////////////////////////message d erreur exemple 2
    Tentative d’upload du fichier : test.jpg
    Fichier de type : image/jpeg
    Ce fichier a l’extension suivante : jpg
    Erreurs : 0

    Notice: Undefined variable: extension_acceptees in C:\xampp\htdocs\secured.php on line 23
    Warning: in_array() expects parameter 2 to be array, null given in C:\xampp\htdocs\secured.php on line 23
    Extension correcte, déplacement du fichier dans le répertoire /upload.
    //////////////////////////////////////////////////////////////////////////////////////////

    //////////////////////////////////////////////////////////////message d erreur exemple 4 le dernier celui qui n as pas de faille..

    Tentative d’upload du fichier : test.jpg
    Fichier de type : image/jpeg
    Ce fichier a l’extension suivante : jpg
    Informations correctes, upload réussi.

    Notice: Undefined index: test in C:\xampp\htdocs\secured.php on line 29
    Warning: move_uploaded_file(NONBRUteForcable/test.jpg): failed to open stream: No such file or directory in C:\xampp\htdocs\secured.php on line 29
    Warning: move_uploaded_file(): Unable to move ‘C:\xampp\tmp\php7CB.tmp’ to ‘NONBRUteForcable/test.jpg’ in C:\xampp\htdocs\secured.php on line 29
    //////////////////////////////////////////////////////////////////////////////////////////

    grand merci d’avance..
    andro

    J'aime

    1. Hello ! Oui je suis au courant que certains fichiers sont bugéus pour la section web… Le fait est que j’ai modifié pas mal de choses sur mon site te je n’ai pas mis à jour le/les fichiers…

      Pour le moment je n’ai pas trop le temps de les patcher désolé… Mais bon, l’explication est là 🙂

      Merci pour ton commentaire et bonne soirée à toi !

      J'aime

Répondre à andro Annuler la réponse.

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google

Vous commentez à l'aide de votre compte Google. Déconnexion /  Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s