Explorer les Vulnerabilites d Upload de Fichiers sans Enfreindre la Loi
Comment contourner les validations d upload dans votre propre lab, cartographier les classes de bugs et durcir les serveurs web contre le RCE par fichier.

Un endpoint d'upload mal configure reste l'un des moyens les moins chers de transformer un humble formulaire en execution de code a distance. Dans un lab bati avec DVWA, OWASP Juice Shop et un reverse proxy Nginx 1.25, nous sommes passes de 'upload d'avatar' a un shell www-data en moins de 40 minutes. Avant tout payload, le contrat compte: la cible est notre propre VM, isolee sur un reseau 10.10.0.0/24 sans route internet, et le but est d'ecrire des regles durcies, pas de collectionner des scalps. Ce parcours couvre toute la surface d'attaque de l'upload de fichiers, chaque classe de bug avec un vecteur, un oracle et le correctif qui la ferme pour de bon.
Le contrat: uniquement en lab autorise
Chaque technique ici est demontree contre une infrastructure que nous possedons et sommes explicitement autorises a casser. Les rejouer contre un SaaS de production 'juste pour verifier' n'est pas du bug bounty, c'est un delit sous la plupart des lois sur l'abus informatique, et la difference entre chercheur et accuse est souvent un scope ecrit et un snapshot de VM. Construis le lab comme le decrit pentest web de zero: Docker sur un bridge isole, aucune route vers l'hote, Burp Suite en proxy interceptant. Prends un snapshot avant chaque run pour prouver exactement ce que tu as touche et revenir proprement en arriere.
Contournement cote client et de blacklist d'extension
La premiere classe de bug est la validation d'extension cote client. Le PHP legacy livre souvent une blacklist (.php, .phtml, .php5) sans vraie verification de Content-Type. Renommer shell.php en shell.pHp.jpg, intercepter avec Burp et forcer le Content-Type a image/jpeg bat environ 80% des filtres amateurs vus dans les CTF d'entreprise. Melanger les casses bat les blacklists naives en minuscules; les points et espaces finaux (shell.php.) battent celles orientees Windows; et des handlers alternes comme .phtml, .phar ou .pht s'executent souvent la ou .php est bloque. L'oracle est simple: demande le chemin uploade et vois si le serveur renvoie ta chaine marqueur ou le code source brut. S'il s'execute, la blacklist etait le seul controle et il a echoue.
Confusion Content-Type et MIME
La deuxieme couche est de faire confiance au type MIME declare par le client. Un backend qui verifie seulement l'en-tete Content-Type du multipart se contourne trivialement, car cet en-tete est controle par l'attaquant; passe-le a image/png et un payload PHP file. Des backends un peu meilleurs appellent getimagesize() ou reniflent les premiers octets, ce qui releve la barre sans la franchir. Le bon oracle en test est de separer type declare et type reel: envoie une vraie extension d'image avec un contenu executable, puis un faux en-tete d'image devant du code, et observe quelle combinaison le serveur stocke et sert comme executable. Tout chemin ou le type declare seul decide de l'acceptation est un finding a rapporter.
Parsing de chemin: doubles extensions et null bytes
Une surface classique est le parsing de chemin du serveur. L'ancien double-parsing de Nginx (CVE-2013-4547 et descendants) laissait un fichier nomme shell.jpg suivi d'un null byte et .php tromper des stacks combinant Nginx et PHP-FPM avec un pathinfo bacle. Dans le lab je l'ai reconstruit avec nginx:1.14-alpine et php:7.2-fpm uniquement en reference historique; avec Nginx 1.25 et cgi.fix_pathinfo=0 le meme payload meurt. La lecon: la securite d'upload ne se decide pas dans l'application seule; le serveur web, le handler FastCGI et le reglage fix_pathinfo decident ensemble si un repertoire d'images peut executer du code. Cet exercice se marie a l'etat d'esprit de parsing d'entree de SQL injection en pratique.
Magic bytes, polyglots et le pipeline d'image
Quand le backend valide les magic bytes, le jeu change. Les polyglots GIF/PHP battent getimagesize() mais echouent face a finfo plus Imagick avec reprocessing, alors l'astuce est d'attaquer le pipeline de transformation plutot que le validateur. ImageMagick avec des policies laxistes parse encore MVG et SVG, et la famille ImageTragick resurgit dans les forks de LMS et CMS. Chez un client fintech j'ai trouve un upload de recu appelant convert sans limites de memoire ni de delegates, ce qui a transforme un SVG en SSRF puis en lecture de fichier. Si le pipeline recupere des ressources distantes, l'upload devient une primitive de request forgery, d'ou le lien direct de cette classe avec SSRF et cloud metadata. Reprocesse chaque image, desactive les coders dangereux et ne laisse jamais convert suivre une URL.
Ou atterrit le fichier: stockage, traversal, service
Une surface sous-estimee est l'endroit ou atterrit le fichier. Le stockage sous /var/www/uploads servi directement par Apache est le classique, mais le meme probleme se cache dans des buckets S3 avec une mauvaise Content-Disposition et dans des CDN qui reprocessent du HTML. Le path traversal dans le nom de fichier (sequences point-point-slash visant /tmp/cron.d/ ou un webroot) marche encore contre un middleware Node qui fait confiance a l'originalname de Multer. L'oracle: uploade un marqueur benin avec un nom de traversal et verifie ou il se materialise sur le disque; s'il echappe au repertoire prevu, l'attaquant choisit l'emplacement d'ecriture. Pour un plan de test plus formel par endpoint, surtout sur les API, suis la checklist de pentest REST et GraphQL.
Fuzzer l'endpoint methodiquement
Les contournements manuels trouvent le premier bug; le fuzzing methodique trouve le reste. Pointe Burp Intruder ou un petit script sur le champ d'upload et itere trois dimensions a la fois: l'extension (une wordlist de .php, .phtml, .phar, .pht, .php7, .inc, melange de casse, point et espace finaux), le Content-Type declare et le prefixe de magic bytes. Pour chaque combinaison, l'oracle est une sonde en deux temps: le serveur accepte-t-il le fichier, et son rappel execute-t-il le marqueur. N'oublie pas la race condition d'upload, ou une webshell est brievement joignable entre l'ecriture et l'etape antivirus ou move; une boucle serree uploader-puis-demander peut gagner cette fenetre sur une vraie cible et doit etre testee explicitement pour justifier le correctif (valider avant que le fichier soit joignable).
Enchainer un upload vers la compromission totale
Un seul marqueur execute est un finding, mais le rapport doit montrer l'impact. Dans le lab, le polyglot GIF/PHP a donne une webshell minimale de commandes; de la une reverse shell standard (un callback bash TCP vers la VM attaquante sur le reseau isole) a fait passer le point d'appui a une session interactive www-data en quelques secondes. L'escalade realiste ensuite n'est pas un exploit de Hollywood mais une persistance ennuyeuse: un repertoire inscriptible servi par le serveur web, un chemin cron world-writable joignable via le traversal precedent, ou un credential de base de donnees fuite dans un fichier de config que la shell peut desormais lire. Documente la chaine jusqu'a l'impact prouve et arrete-toi; l'objectif est de justifier la note de severite, pas de saccager une machine, et le snapshot te permet de rejouer toute la chaine pour le client sans laisser de degats.
Regles defensives qui marchent vraiment
Cote defense, les regles qui gagnent leur place dans un rapport sont ennuyeuses et efficaces. Genere un nom de fichier aleatoire (UUIDv7) et jette entierement le nom client; stocke hors du document root; sers via un handler qui force Content-Disposition: attachment et Content-Type: application/octet-stream; valide le type MIME avec libmagic cote serveur, pas l'en-tete declare; reprocesse les images et retire les metadonnees; et bloque les doubles extensions au serveur web lui-meme, par exemple en refusant toute requete dont le chemin contient .php, .phtml ou .phar avant un autre point. Ajoute un passage antivirus et un plafond de taille, et mets le tout derriere les gates shift-left de AppSec shift-left pour qu'une regression tombe en CI, pas en production.
Checklist
Avant de valider une fonctionnalite d'upload: le nom de fichier est genere par le serveur et ne reflete jamais l'entree utilisateur; l'extension est en allowlist, pas en blacklist; le MIME est verifie par contenu, pas par en-tete; le stockage est hors du webroot et servi en disposition attachment; les images sont reprocessees et les metadonnees retirees; le serveur web n'execute rien dans le chemin d'upload; le path traversal dans le nom est neutralise; des limites de taille et de debit existent; et un antivirus ou scanner de contenu tourne en asynchrone. Chaque 'non' de cette liste est un finding, et chaque finding recoit une ligne de remediation concrete, pas un vague 'sanitiser l'entree'.
FAQ
Bloquer .php suffit-il pour etre en securite? Non; une blacklist est le controle le plus faible possible, battu par le melange de casse, les handlers alternes (.phtml, .phar, .pht), les points finaux et les bizarreries de parsing de chemin du serveur. Le controle durable est une allowlist d'extensions combinee a une validation MIME par contenu et un serveur web qui n'execute jamais de code dans le repertoire d'upload. Puis-je autoriser les uploads SVG en securite? Seulement avec grande prudence: SVG est du XML et peut porter du script et des references externes, alors sers-le avec une Content-Security-Policy restrictive et une disposition attachment, ou rasterise-le en PNG a la reception et jette l'original. Traiter le SVG comme une image inoffensive, c'est ainsi qu'ImageTragick et le XSS stocke continuent d'atterrir.
Les uploads doivent-ils vivre sur le meme hote que celui qui sert l'app? Prefere un domaine de stockage separe sans execution de code et un handler de service verrouille, pour qu'une ecriture meme reussie ne devienne pas un RCE sur l'hote applicatif. Et comment tester sans risquer de degats? Prends un snapshot de la VM, travaille uniquement dans le scope autorise, utilise des payloads marqueurs benins qui prouvent le bug (une chaine unique, l'id propre de la VM) plutot que d'exfiltrer de vraies donnees, et reviens en arriere apres chaque run; prouver l'ecriture suffit, lire /etc/passwd sur la machine d'autrui est inutile et illegal.
Conclusion
L'upload de fichiers reste dangereux parce qu'il se tient a la couture de trois systemes, l'application, le serveur web et la couche de stockage, et une faille dans l'un suffit. Attaque-le comme ce lab, classe par classe, avec un oracle clair pour chacune, et les regles defensives s'ecrivent d'elles-memes: noms generes par le serveur, extensions en allowlist, verifications MIME par contenu, images reprocessees, aucune execution dans le chemin d'upload et traversal neutralise. Garde chaque experience dans le scope autorise avec un snapshot pour le prouver, livre les regles durcies via CI, et l'humble formulaire d'avatar cesse d'etre un chemin de 40 minutes vers un shell.


