Bonjour, je possède un petit site avec pas mal d'utilisateurs, j'ai récemment ajouté l'option de pouvoir uploader une photo.
Le problème est que quelques jours après l'ajout de cette fonctionnalité, j'ai reçu un email stipulant que mon serveur avait été attaqué et que l'attaquant avait upload un fichier .php via ce formulaire.

Comment le rendre entièrement sécurisé pour faire en sorte que l'on ne puisse envoyer QUE des fichiers photo et vidéo ?

if($_SERVER["REQUEST_METHOD"] == "POST"){
    // Vérifie si le fichier a été uploadé sans erreur.
    if(isset($_FILES["new_avatar"]) && $_FILES["new_avatar"]["error"] == 0){
        $allowed = array("jpg" => "image/jpg","JPG" => "image/JPG", "jpeg" => "image/jpeg", "gif" => "image/gif", "png" => "image/png", "mov" => "video/quicktime");
        $filename = $_FILES["new_avatar"]["name"];
        $filetype = $_FILES["new_avatar"]["type"];
        $filesize = $_FILES["new_avatar"]["size"];

        // Vérifie l'extension du fichier
        $ext = pathinfo($filename, PATHINFO_EXTENSION);
        if(!array_key_exists($ext, $allowed)) die("Erreur : Veuillez sélectionner un format de fichier valide.");

        // Vérifie la taille du fichier - 5Mo maximum
        $maxsize = 5 * 4084 * 4084;
        if($filesize > $maxsize) die("Error: La taille du fichier est supérieure à la limite autorisée.");

        // Vérifie le type MIME du fichier
        if(in_array($filetype, $allowed)){
            // Vérifie si le fichier existe avant de le télécharger.
            if(file_exists("assets/img/photos_pre_inscriptio/" . $_FILES["new_avatar"]["name"])){
                echo $_FILES["new_avatar"]["name"] . " existe déjà.";
            } else{
                move_uploaded_file($_FILES["new_avatar"]["tmp_name"], "assets/img/photos_pre_inscription/" . $_FILES["new_avatar"]["name"]);

                $NouvelAvatar = "assets/img/photos_pre_inscription/".$_FILES["new_avatar"]["name"];

        $donnees = [ 
              'id' => 0, 
              'source' => $NouvelAvatar,
              'modele' => $_POST['modele'],
              'username' => $_SESSION['id']
                ];

                $sth = $bdd->prepare("INSERT INTO photo_pre_inscription VALUES (:id, :source, :modele, :username)"
                );
                $sth->execute($donnees);

        header("Location: ajout_photos.php");

            }

Le formulaire :

<form enctype="multipart/form-data" action="ajout_photos.php" id="formAvatar" method="POST">

<input type="hidden" name="MAX_FILE_SIZE" value="10000000" />

<input id="upload" name="new_avatar"type="file" accept="video/*,image/*" class="form-control-file">
<br>
<input type="text" name="modele" class="form-control">
<br>
<input type="submit" name="submit" class="btn btn-success" value="Envoyer">
</form>

Merci à vous !

2 réponses


salut Toinou !
voici quelques pistes que je pourrais te donner.
d'abord pour sélectionner un format de fichier valide. je te conseille de tester le type MIME de ton fichier d'upload et non pas l'extension. et aussi de deviner ce type mime en utilisant la classe finfo. En gros cela détermine le type MIME à partir du contenu du fichier et non à partir de l'extension.

// pour un fichier script.exe renommé en script.jpg

$filetype = $_FILES["new_avatar"]["type"]; // "image/jpeg"

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $_FILES["new_avatar"]["tmp_name"]); // "application/x-dosexec"

du coup faire ton contrôle de format de fichier à partir de ce $mimeType.

tu peux voir une implémentation complète (et non grossière comme je te l'ai fait ci dessus) d'un FileinfoMimeTypeGuesser avec le framework Symfony : FileinfoMimeTypeGuesser.php

ensuite je te conseille de ne pas stocker le fichier avec le nom fournit par l'utilisateur mais d'abord de filtrer ce nom pour retirer tous caractères spéciaux, indésirables et suceptibles de te créer des problèmes par la suite.
au besoin tu peux stocker dans ta base de donnée le nom de fichier à lui (pour pouvoir lui restituer) et le nom de fichier réel
Tu as cette classe qui fait très bien le travail Urlizer


$normalizedFilename = Urlizer::urlize($filename);
// 'Nom de fichier !' devient 'nom-de-fichier'

et enfin d'y suffixer l'extension devinée à partir du type Mime et non l'extension proposée par l'utilisateur

$extensions = [
    'image/jpeg' => 'jpg',
    ...
];

$completeFilename = $normalizedFilename.'.'.$extensions[$mimeType];
move_uploaded_file($_FILES["new_avatar"]["tmp_name"], "assets/img/photos_pre_inscription/" . $_FILES["new_avatar"]["name"]);

voilà quelques pistes. j'imagine qu'il peut y en avoir d'autres, que dit la communauté ?

Bonjour.
J'espère que l'utilisation de die n'est que pendant tes tests, sinon ils n'ont aucune logique, car même si tu affiches un message à l'utilisateur avec, il ne peut rien faire puisqu'il ne peut même pas voir le formulaire pour faire les modifications et resoumettre le formulaire.
En ce qui concerne le nom du fichier, je déconseille de juste faire un slug du nom du fichier d'origine, car pour peu que deux utilisateurs soummetent un fichier avec le même nom, un des deux fichiers sera écrasé.
Il est donc soit préférable d'utiliser l'identifient de l'utilisateur au niveau de la base de données, soit un identifiant arbitraire qui sera unique, par conséquent de type uuid.