Un formulaire de téléversement qui accepte des images est un moyen de poser un fichier sur votre serveur. La validation réduit ce qui passe ; elle ne rend pas le répertoire sûr. Ce qui le rend sûr, c'est que le serveur web refuse d'EXÉCUTER quoi que ce soit là-dedans, quel que soit le fichier.

Nginx

location ^~ /uploads/ {
    location ~ \.php$ { deny all; }
    add_header X-Content-Type-Options nosniff;
}

Apache et LiteSpeed

# /var/www/site/uploads/.htaccess
php_flag engine off
<FilesMatch "\.(php|phar|phtml|cgi|pl)$">
    Require all denied
</FilesMatch>
Options -ExecCGI -Indexes

Validez aussi, et correctement

  • Vérifiez le vrai type, pas le nom envoyé par un navigateur : finfo_file() en PHP.
  • Renommez chaque téléversement avec un nom que vous avez généré. Ne conservez jamais le nom de fichier du visiteur.
  • Refusez les doubles extensions - un fichier nommé invoice.php.jpg n'est pas un hasard.
  • Réencodez les images. Faire passer une photo par une bibliothèque d'images en retire tout ce qui y était caché.
$f = new finfo(FILEINFO_MIME_TYPE);
$type = $f->file($tmp);
if (!in_array($type, ['image/jpeg','image/png','image/webp'], true)) {
    // reject
}
$name = bin2hex(random_bytes(16)) . '.webp';

Mieux encore : rangez-les là où le web ne va pas

Gardez les téléversements hors de la racine web et servez-les par un script qui vérifie qui demande. Une milliseconde de plus, et il n'existe plus aucune URL capable d'exécuter quoi que ce soit.

Testez-le. Déposez vous-même un fichier PHP inoffensif dans le répertoire de téléversements et demandez-le : vous devez obtenir un 403 ou le texte source, jamais la sortie. Cela prend une minute et c'est la seule preuve.