Lorsque l'on crée une application web il est souvent nécessaire de mettre en place des systèmes permettant d'envoyer des fichiers sur le serveur (document, images...). Ces système d'envoi de fichier sont un vecteur d'attaque important et l'impact peut être désastreux.
Le principe
Si on laisse l'utilisateur envoyer n'importe quel type de fichier il peut envoyer un fichier qu'il pourra ensuite exécuter pour gagner en privilège (lire des fichiers sensible, modifier des fichiers...). PHP est particulièrement sensible à ce genre d'attaque car il est possible d'exécuter un script par une simple URL.
Prenons un cas concret, l'utilisateur envoie un fichier hack.php
à la place de son avatar.
if (!empty($_FILES)) {
$extension = pathinfo($_FILES['avatar']['name'], PATHINFO_EXTENSION);
$avatarPath = __DIR__ . '/images/' . $userID . '.' . $extension
move_uploaded_file($_FILES['avatar']['tmp_name'], $avatarPath);
// ...
}
Le fichier est alors déplacé dans un dossier images
avec son extension original. Si maintenant je me rend sur l'URL de mon avatar /images/{ID}.php
le code à l'intérieur de mon fichier PHP sera exécuté. On peut par exemple déplacer un fichier qui permettra de manipuler le système de fichier pour lire le code source de l'application.
La solution
Il y a 2 axes de défense contre ce type d'attaque :
- Meilleur contrôle des fichiers que l'on peut téléverser dans l'application.
- Limiter l'exécution de fichiers à certains dossiers.
Contrôler les fichiers
Lorsque l'on autorise l'utilisateur à envoyer des images ou des documents, il est nécessaire d'effectuer des contrôles lors de l'envoi de fichier.
$allowedTypes = ["image/jpeg", "image/png"];
$allowedExtensions = ['png', 'jpg', 'jpeg'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES["avatar"]["tmp_name"]);
$extension = strtolower(pathinfo($_FILES["avatar"]['name'], PATHINFO_EXTENSION));
if (
!in_array($mime, $allowedTypes) ||
!in_array($extension, $allowedExtensions)
) {
throw new \Exception('Type de fichier non accepté');
}
// On peut continuer
Attention, on ne se contentera pas de vérifier le type MIME d'un fichier car il est possible de le fausser. Si on conserve l'extension originale on la validera aussi.
Limiter l'exécution de script
Un autre axe de défense consiste à limiter l'exécution de scripts depuis une URL particulière.
Dans le cas d'Apache on peut ajouter un fichier .htaccess
dans un dossier dans lequel on n'attend pas de scripts exécutable.
<FilesMatch "\.php.*$">
Order allow,deny
Deny from all
</FilesMatch>
Pour nginx on peut ajouter une règle directement au niveau de la configuration.
server {
# ...
location /images {
location ~* \.php {
deny all;
}
}
# ...
}