Sécuriser un formulaire d'upload

Voici comment je sécuriserais un formulaire d’upload. Pour commencer il faudra créer une liste de type MIME que l’on accepte à l’upload:

<?php
# Liste des types MIME acceptés
$type_mime_acceptes = array("image/jpg", "image/jpeg", "image/gif", "image/png");
?>

Ensuite je créerais un tableau associatif qui contient les extensions des différents type MIME acceptés :

<?php
# Liste des types MIME acceptés
$type_mime_acceptes = array("image/jpg", "image/jpeg", "image/gif", "image/png");

$extensions_acceptees = array();
$extension_acceptees["image/jpg"] = ".jpg";
$extension_acceptees["image/jpeg"] = ".jpeg";
$extension_acceptees["image/gif"] = ".gif";
$extension_acceptees["image/png"] = ".png";
?>

Si le type MIME reçu est dans la whitelist alors j’utilise la fonction explode afin de casser le nom du fichier au niveau du/des « . » :

<?php
# Liste des types MIME acceptés
$type_mime_acceptes = array("image/jpg", "image/jpeg", "image/gif", "image/png");

$extensions_acceptees = array();
$extension_acceptees["image/jpg"] = ".jpg";
$extension_acceptees["image/jpeg"] = ".jpeg";
$extension_acceptees["image/gif"] = ".gif";
$extension_acceptees["image/png"] = ".png";

if(in_array($_FILES["file"]["type"], $type_mime_acceptes)){
  $extension = explode(".", $_FILES["file"]["name"]);	 
}
else{
  echo "Did you just try to hack me ? :D";
}
?>

La fonction explode retourne un tableau. L’élément tab[0] contient le nom du fichier tandis que les éléments tab[1], tab[2] etc … Contiennent les différentes extensions. Si le tableau est plus grand que 2 cela veut dire qu’on a probablement tenté une attaque par double extension. Donc on bloque l’upload.

Si le tableau ne contient que deux éléments alors je prends l’élément tab[0] que je concatène à son extension en me basant sur le tableau associatif précédemment déclaré :

<?php
# Liste des types MIME acceptés
$type_mime_acceptes = array("image/jpg", "image/jpeg", "image/gif", "image/png");

$extensions_acceptees = array();
$extension_acceptees["image/jpg"] = ".jpg";
$extension_acceptees["image/jpeg"] = ".jpeg";
$extension_acceptees["image/gif"] = ".gif";
$extension_acceptees["image/png"] = ".png";

if(in_array($_FILES["file"]["type"], $type_mime_acceptes)){
  $extension = explode(".", $_FILES["file"]["name"]);
  if(count($extension) == 2){
    echo "Information correction, upload réussi<br>";
    move_uploaded_file($_FILE["file"]["tmp_name"], "upload/".$FILES["file"]["name"].$extension_acceptes[$extension[0]]);
  }
  else{
    echo "Come on man, double extension exploits is oooooooooooold";
  }
}
else{
  echo "Did you just try to hack me ? :D";
}
?>

En faisant cette opération j’empêche l’utilisateur de modifier son type MIME et par la même occasion je force le fichier à avoir une extension valide.

Il serait bien aussi de vérifier que la taille du fichier uploadé ne dépasse par les 10Mo. Pour cela on pourra se servir de la fonction getimagesize() :

<?php
# Liste des types MIME acceptés
$type_mime_acceptes = array("image/jpg", "image/jpeg", "image/gif", "image/png");

$extensions_acceptees = array();
$extension_acceptees["image/jpg"] = ".jpg";
$extension_acceptees["image/jpeg"] = ".jpeg";
$extension_acceptees["image/gif"] = ".gif";
$extension_acceptees["image/png"] = ".png";

if(in_array($_FILES["file"]["type"], $type_mime_acceptes)){
  $extension = explode(".", $_FILES["file"]["name"]);
  $taille = getimagesize($_FILES["file"]["tmp_name"])[0];
  if(count($extension) == 2 && $taille < 10000000){
    echo "Information correction, upload réussi<br>";
    move_uploaded_file($_FILE["file"]["tmp_name"], "upload/".$FILES["file"]["name"].$extension_acceptes[$extension[0]]);
  }
  else{
    echo "Come on man, double extension exploits is oooooooooooold";
  }
}
else{
  echo "Did you just try to hack me ? :D";
}
?>

Enfin on oublie pas de déplacer le fichier dans un répertoire introuvable/touchable. L’idéal étant de déplacer le fichier dans un répertoire qui n’est pas accessible depuis le web (il faudra donc cloisonner l’utilisateur au répertoire /html par exemple). et de renommer le fichier uploadé (par son hash par exemple).

Et voici le code final :

<?php
# Liste des types MIME acceptés
$type_mime_acceptes = array("image/jpg", "image/jpeg", "image/gif", "image/png");

$extensions_acceptees = array();
$extension_acceptees["image/jpg"] = ".jpg";
$extension_acceptees["image/jpeg"] = ".jpeg";
$extension_acceptees["image/gif"] = ".gif";
$extension_acceptees["image/png"] = ".png";

if(in_array($_FILES["file"]["type"], $type_mime_acceptes)){
  $extension = explode(".", $_FILES["file"]["name"]);
  $taille = getimagesize($_FILES["file"]["tmp_name"])[0];
  if(count($extension) == 2 && $taille < 10000000){
    echo "Information correction, upload réussi<br>";
    move_uploaded_file($_FILE["file"]["tmp_name"], "NOnBRUteForcable/".$FILES["file"]["name"].$extension_acceptes[$extension[0]]);
  }
  else{
    echo "Come on man, double extension exploits is oooooooooooold";
  }
}
else{
  echo "Did you just try to hack me ? :D";
}
?>

Avec ça il me semble compliquer de pouvoir utiliser un formulaire d’upload à des fins frauduleuses. Si vous pensez avoir trouver une faille n’hésitez pas à m’en faire part  !