Empêcher le téléchargement direct de fichiers

Formation

En Ligne

Prix sur demande

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Description

  • Typologie

    Formation

  • Méthodologie

    En ligne

Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.

Questions / Réponses

Ajoutez votre question

Nos conseillers et autres utilisateurs pourront vous répondre

À qui souhaitez-vous addresser votre question?

Saisissez vos coordonnées pour recevoir une réponse

Nous ne publierons que votre nom et votre question

Les Avis

Le programme

Introduction du cours

Bonjour à toutes et à tous ! :)

Si vous êtes ici, c'est que vous avez un site internet sur lequel se trouvent des fichiers dont vous voulez maîtriser le téléchargement. Beaucoup de sites disposent d'une page qui vous donne accès aux fichiers que vous souhaitez télécharger ; le plus souvent, elle ne fait que vous rediriger vers le fichier ciblé à coups de :

<?php header('Location: http://www.monsite.com/fichiers/le_fichier_a_telecharger'); ?>

et permet à l'administrateur, par exemple, de compter le nombre de fois qu'il a été téléchargé via une base de données ou un fichier texte. Une telle redirection présente un inconvénient majeur : le lien direct vers le fichier est révélé à l'utilisateur, qui peut donc le télécharger sans passer par la page de votre site prévue à cet effet. Ce qui fausse toutes vos statistiques et conduit même au « vol de fichiers », c'est-à-dire que d'autres administrateurs peuvent rendre publics les liens directs de vos fichiers sans que leurs utilisateurs ne sachent qu'ils sont hébergés sur votre site ! Malgré le trafic ainsi généré, ces personnes ne visitent pas votre site.

Heureusement, ce tutoriel vous apprendra une méthode efficace pour résoudre ce problème et vous épargnera, je l'espère, de mauvaises surprises.

La page de téléchargement

Avant de commencer à sécuriser quoi que ce soit, nous allons voir à quoi ressemble notre page de téléchargement — ou devrais-je dire nos pages —, car nous allons prendre l'habitude de séparer les différents fichiers afin de ne pas mélanger PHP et HTML.

Voici donc notre page telecharger.php :

<?php // Si l'utilisateur a demandé le téléchargement d'un fichier if(!empty($_GET['fichier'])) // On lance le téléchargement du fichier else require('erreur.php'); ?>

Ici, rien de bien compliqué : nous vérifions seulement si le téléchargement a été demandé via une variable $_GET['fichier']. Si oui, nous le lançons (nous verrons comment par la suite) ; sinon, nous afficherons un message prévenant l'utilisateur qu'il n'a demandé le téléchargement d'aucun fichier.

Afin de faire quelque chose d'à peu près propre, ledit message sera inclus dans une page, que nous pourrions nommer erreur.php, grâce à la fonction require() :

<!DOCTYPE html> <html> <head> <title>Téléchargement d'un fichier</title> </head> <body> <p> Désolé, ce fichier n'existe pas. </p> </body> </html>

La partie qui nous intéresse est celle dans le cas où l'on souhaite télécharger un fichier, donc quand la variable $_GET['fichier'] n'est pas nulle, dans le fichier telecharger.php.

Avant, la méthode consistait à rediriger directement l'utilisateur sur le fichier qu'il avait demandé. En imaginant que le dossier qui contient tous vos fichiers à télécharger ait pour nom fichiers et qu'il se trouve à la racine, nous aurions fait quelque chose de ce type :

<?php if(!empty($_GET['fichier'])) { $chemin = 'fichiers/' . $_GET['fichier']; if(file_exists($chemin)) header('Location: http://monsite.com/' . $chemin); else require('erreur.php'); } else require('erreur.php'); ?>

Nous venons d'utiliser la fonction file_exists(), qui vérifie que le fichier existe bien avant d'en faire quoi que ce soit.

Ainsi, dans le cas où un fichier test.txt, par exemple, se trouverait dans le dossier des fichiers (fichiers/), nous aurions seulement à donner à l'utilisateur le lien http://www.monsite.com/telecharger.php?fichier=test.txt pour qu'il puisse le télécharger (ou du moins l'ouvrir, dans le cas de notre fichier texte). Nous pourrions donc, pour reprendre l'exemple du début, rajouter un compteur afin de connaître le nombre de fois qu'il a été téléchargé.

<?php if(!empty($_GET['fichier'])) { $chemin = 'fichiers/' . $_GET['fichier']; if(file_exists($chemin)) { // On incrémente le nombre de fois que le fichier a été téléchargé ! incrementer_compteur($chemin); // C'est cette fonction qui va s'en occuper ! header('Location: http://monsite.com/' . $chemin); } else require('erreur.php'); } else require('erreur.php'); ?>

Cependant, cette méthode ne résout pas le problème du vol de fichiers puisqu'elle communique à l'utilisateur le lien direct vers le fichier qu'il souhaite télécharger. Il lui sera non seulement possible d'y accéder de nouveau sans passer par la page de téléchargement, mais votre compteur ne vous sera plus d'aucune utilité. Nous allons voir tout de suite comment empêcher cela.

Protégeons nos fichiers

Il est temps de vous expliquer comment protéger nos fichiers. Et là, le PHP ne pourra malheureusement rien pour nous : la protection des fichiers concerne directement le serveur. Pour lui donner des instructions, il faut modifier le .htaccess.

Je ne vous expliquerai pas le fonctionnement d'un fichier .htaccess, ni même ne rentrerai dans les détails de ce qu'il est possible de faire grâce à lui, puisque d'autres l'ont fait avant moi. Je vais simplement vous dire en quoi il nous sera utile. Pour faire court, une des possibilités du .htaccess est d'empêcher l'accès à des dossiers entiers avec ce petit bout de texte :

Deny From All

En ajoutant ce code à un fichier texte nommé .htaccess et en plaçant ce dernier dans le dossier de votre choix, vous tomberez sur une jolie erreur 403 : Forbidden (ce qui signifie « Interdit », pour les anglophobes) si vous essayez d'atteindre un élément présent dans ce dossier ou dans un de ses sous-dossiers.

Forbidden You don't have permission to access [chemin du fichier] on this server.

Youhouhou, c'est exactement ce que nous voulions ! Maintenant, si ces vilains fraudeurs veulent accéder directement aux fichiers, ils ne le pourront plus.

C'est génial ! Mais… Euh… La page de téléchargement ne fonctionne plus… C'est normal ?!

Bien sûr, notre page ne fait que rediriger l'utilisateur vers le fichier qui va bien. Donc, si nous empêchons l'accès à ces fichiers, il ne pourra pas non plus les télécharger en passant par notre page. Ce qui n'est pas sans poser problème, n'est-ce pas ? :p Heureusement, le but de ce tutoriel est de vous expliquer comment contourner cette protection.

Je préfère que les choses soient claires. TOUS les fichiers qui se trouvent dans le même dossier que le .htaccess, ainsi que les fichiers situés dans d'éventuels sous-dossiers, seront inaccessibles.
Par conséquent, il est primordial de n'y placer aucun fichier PHP. Ces dossiers doivent uniquement contenir les fichiers que vos utilisateurs sont autorisés à télécharger.

Passer outre la protection

Tel est notre but : faire en sorte de garder la protection .htaccess tout en autorisant le téléchargement grâce à notre page. Pour cela, vous devez savoir que les fonctions PHP se servant de fichiers ne prennent pas en compte les protections appliquées par un .htaccess. Ainsi, il est possible d'ouvrir, de modifier, voire de supprimer un fichier censé être protégé par ce même .htaccess.

Là où ça nous arrange, c'est qu'il existe une fonction readfile() qui permet, comme le dit la documentation, de lire un fichier et de l'envoyer dans le buffer de sortie. On ne redirige donc plus vers le fichier, on prend carrément son contenu !

En reprenant le code précédent, nous pouvons donc remplacer le header() (la redirection) par ceci :

<?php if(!empty($_GET['fichier'])) { $chemin = 'fichiers/' . $_GET['fichier']; if(file_exists($chemin)) readfile($chemin); else require('erreur.php'); } ?>

Super ! Mais… Euh… Quand j'essaye de télécharger une image, de nombreux symboles bizarres s'affichent. C'est normal ?!

Encore une fois, oui, c'est tout à fait normal. Vous avez déjà tenté d'ouvrir une image avec le bloc-notes ? Eh bien, cela devrait donner la même chose ! En effet, readfile() ne fait que lire le contenu du fichier et ne cherche pas à l'afficher en fonction de son type.

Cela dit, malgré le .htaccess, le contenu de notre fichier est bien lu et affiché. La solution que nous allons employer pour ne plus afficher le contenu du fichier est d'en forcer le téléchargement.

Forcer le téléchargement des fichiers

Voici une petite astuce très pratique pour forcer le téléchargement de n'importe quel fichier, astuce qui pourra d'ailleurs résoudre le problème précédent.

Vous avez dû remarquer à plusieurs reprises que, quelle que soit la technique utilisée, sécurisée ou non, tous les fichiers qui peuvent être ouverts par le navigateur le sont effectivement — à condition, bien entendu, de ne pas avoir modifié la configuration du navigateur. Donc, en temps normal, plutôt que de télécharger un fichier au format texte ou XML, une image, un PDF et beaucoup d'autres, votre navigateur l'ouvrira. Vous pouvez ensuite télécharger le fichier en question grâce à un clic droit puis à « Enregistrer sous », mais ce n'est vraiment pas pratique.

Néanmoins, il est possible, avec les en-têtes HTTP, de forcer la main aux navigateurs et de les obliger à télécharger ledit fichier avec ces quelques lignes :

<?php header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename=' . basename($chemin)); header('Content-Transfer-Encoding: binary'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Pragma: public'); header('Content-Length: ' . filesize($chemin)); readfile($chemin); exit; ?>

Ce code est extrait de la documentation PHP de la fonction readfile() et n'a subi qu'une simple modification. En effet, la gestion de la tamporisation de sortie est devenue inutile dans notre cas.

Ne vous inquiétez pas, je ne vais pas vous laisser avec ce code sans aucune explication.

Pour commencer, notons qu'il y a beaucoup d'appels à la fonction header(). Nous l'avons vu au début de ce cours, cette fonction assure une redirection (vers un fichier par exemple) si elle est utilisée avec 'Location: [adresse de redirection]'. Elle permet en réalité bien plus que cela, notamment de modifier l'en-tête HTTP reçu par le navigateur. Ainsi, vous pourrez facilement lui dire qu'il s'agit d'un fichier à télécharger et l'obliger à l'interpréter comme tel.

Voici maintenant le détail de chacun de ces header() :

  • Ligne 2 : spécifie au navigateur que les données qu'il va recevoir doivent être considérées comme un fichier à télécharger.

  • Ligne 3 : indique que le flux de données qui va suivre est de type « flux d'octet ». Comme n'importe quel fichier peut être considéré ainsi, il n'est pas nécessaire d'en connaître le type MIME exact (qui diffère en fonction de l'extension).

  • Ligne 4 : attribue un nom au fichier. Par conséquent, le nom qui apparaîtra dans la popup de téléchargement sera celui indiqué après filename=.

  • Ligne 5 : précise que le fichier à traiter devra être envoyé en binaire. En d'autres termes, les données seront conservées telles quelles afin d'éviter les problèmes d'encodage et de « transformation » non voulus.

  • Lignes 6 à 8 : ces lignes ordonnent au navigateur de ne pas mettre les fichiers en cache pour que le téléchargement soit déclenché à chaque fois.

  • Ligne 9 : donne au navigateur la taille du fichier, sans laquelle il ne pourrait afficher correctement la barre de progression ni donner le pourcentage déjà téléchargé — et encore moins estimer le temps restant.

Pour aller un peu plus loin avec les en-têtes HTTP, vous trouverez d'autres informations sur ce site.

En ce qui concerne les fonctions PHP maintenant : basename() permet, à partir d'un chemin, de récupérer uniquement le nom d'un fichier ; filesize() (les anglophones l'auront déjà deviné) en indique la taille. Quant à la fonction exit(), qui clôt cette série d'appels, elle sert à stopper l'exécution du script pour ne pas rajouter de données à la suite du fichier sous peine de le corrompre. De plus, il est inutile de charger le reste d'une page qui ne sera, en théorie, pas visible.

Pour continuer à garder un code clair et propre, nous allons créer une fonction, appelée readfile(), à la seule fin d'envoyer le header personnalisé et même de faire la vérification.

Vous devez certainement déjà posséder un fichier fonctions.php dans lequel vous mettez toutes les fonctions de votre site. Il vous suffit de lui ajouter la fonction suivante :

<?php function telecharger_fichier($fichier) { $chemin = 'fichiers/' . $fichier; if(file_exists($chemin)) { header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename=' . basename($chemin)); header('Content-Transfer-Encoding: binary'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Pragma: public'); header('Content-Length: ' . filesize($chemin)); readfile($chemin); exit; } else require('erreur.php'); } ?>

Si vous voulez faire en sorte d'afficher les images plutôt que d'obliger l'utilisateur à les télécharger, vous pouvez modifier cette fonction comme ceci :

<?php function telecharger_fichier($fichier) { $chemin = 'fichiers/' . $fichier; $images_ext = array('jpg', 'jpeg', 'png', 'bmp', 'gif'); if(file_exists($chemin)) { $image_format = substr(strrchr($chemin, '.'), 1); if(in_array($image_format, $images_ext)) header('Content-Type: image/' . $image_format); else { header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename=' . basename($chemin)); header('Content-Transfer-Encoding: binary'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Pragma: public'); header('Content-Length: ' . filesize($chemin)); } readfile($chemin); exit; } else require('erreur.php'); } ?>

Le principe de cette méthode est d'interdire l'accès direct à vos images (clic droit → « Afficher l'image ») tout en permettant leur affichage. Par contre, vu que c'est du bonus, je n'expliquerai pas en détail les rajouts. S'il y a quelque chose que vous ne comprenez pas, faites-le-moi savoir par MP ou dans les commentaires. Sinon, la documentation est toujours là pour vous aider. ;)

Ce qui nous amène à la modification suivante de notre fichier telecharger.php :

<?php if(!empty($_GET['fichier'])) { // N'oubliez pas d'inclure le fichier qui contient notre fonction ! include('fonctions.php'); // On appelle la fonction qu'on a créée juste avant ! telecharger_fichier($_GET['fichier']); } else require('erreur.php'); ?> Une faille particulièrement dangereuse

Nous avons désormais un script qui fonctionne très bien. Malheureusement, et vous ne vous en êtes peut-être pas rendu compte, il possède une énorme faille…

Oui, imaginez qu'un utilisateur malveillant décide de se rendre sur cette page : http://www.monsite.com/telecharger.php [...] =../index.php, il sera alors en mesure de télécharger directement le contenu de votre page d'accueil. Pire, avec cette technique, l'ensemble des fichiers de votre site sera à sa disposition et, pour peu qu'il...

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Empêcher le téléchargement direct de fichiers

Prix sur demande