Le téléversement sécurisé de fichiers en PHP ne consiste pas à accepter un formulaire et à déplacer un fichier sur le serveur. Une pièce jointe peut devenir un vecteur d’exécution, une fuite d’informations, une charge qui épuise les ressources ou un document inaccessible lorsque le processus métier en a besoin. La conception doit couvrir le cycle complet : réception, validation, stockage, traitement, autorisation d’accès, audit et suppression.
Définir le contrat métier avant d’accepter des fichiers

Le premier contrôle n’est pas technique : il consiste à limiter le besoin auquel répond chaque pièce jointe. Un justificatif d’identité, une facture et une image de profil ont des formats, des propriétaires, des périodes de conservation et des autorisations de consultation différents. Les regrouper sous une option générique de « téléverser un fichier » rend difficile l’application de contrôles adaptés.
Pour chaque type de document, définissez un contrat explicite :
- Quels formats sont nécessaires et lesquels sont exclus.
- Taille maximale, nombre maximal de pièces jointes par opération et quota cumulé par compte ou dossier.
- Qui est autorisé à le téléverser, à quelle étape du processus, et s’il peut le remplacer ou le supprimer.
- Quelle validation automatique et quelle revue humaine il nécessite.
- Qui peut le voir, le télécharger ou demander une nouvelle version.
- Combien de temps il est conservé et quel événement déclenche sa suppression.
Ce contrat évite d’accepter des fichiers « au cas où ». Il permet également de distinguer un échec de validation, que l’utilisateur peut corriger, d’une restriction métier, comme tenter de joindre un document lorsque le dossier est déjà clôturé.
Pourquoi l’extension et le type déclaré ne suffisent pas
L’extension du nom d’origine et le type envoyé dans $_FILES['type'] sont des données fournies par le client. Ils servent d’informations auxiliaires pour l’interface, mais ne prouvent pas le contenu. Renommer un fichier est trivial et un client peut envoyer n’importe quel en-tête HTTP.
La validation technique doit recourir à une défense en profondeur. En PHP, détectez le type à partir des octets reçus avec des mécanismes tels que finfo ; appliquez ensuite des validateurs propres au format lorsque le risque ou l’usage l’exigent. Pour une image destinée à être affichée, il ne suffit pas de l’identifier comme une image : il est préférable de la décoder au moyen d’une bibliothèque adaptée et de générer une nouvelle représentation. Pour un PDF ou un document bureautique, validez la structure attendue avec des outils spécialisés dans un environnement isolé.
Une liste d’autorisations est plus sûre qu’une liste de blocage. Si le cas d’usage accepte JPEG et PNG, rejetez le reste au lieu de tenter d’énumérer les formats dangereux. Les fichiers compressés requièrent un contrôle supplémentaire : une limite de taille compressée ne limite pas l’expansion lors de la décompression. Définissez des plafonds de taille décompressée, de nombre d’entrées, de profondeur d’imbrication et de temps d’analyse.
Les noms ne sont pas fiables non plus. Ne les utilisez ni comme chemin, ni comme identifiant, ni comme nom physique. Les séquences telles que ../, les caractères de contrôle, les doubles extensions et les collisions de noms doivent perdre leur importance, car le système génère son propre identifiant opaque.
Réception contrôlée et gestion des erreurs partielles
Avant de traiter le contenu, limitez la surface d’entrée. Configurez des limites cohérentes dans PHP, le serveur web et le proxy inverse. Si le proxy accepte 100 Mo mais que PHP en accepte 10 Mo, le comportement sera déroutant ; si PHP autorise davantage que prévu, un attaquant peut consommer de la mémoire, du disque temporaire ou des connexions de travail.
Contrôlez explicitement la taille individuelle, le total de la requête, le nombre de fichiers et la durée du téléversement. Vérifiez le code d’erreur de chaque entrée de $_FILES, assurez-vous qu’elle provient d’un téléversement HTTP et traitez chaque pièce jointe comme une unité indépendante. Dans une opération multiple, décidez à l’avance si le résultat est atomique ou partiel. Si des résultats partiels sont acceptés, la réponse doit indiquer précisément quel fichier a été reçu, lequel a été rejeté et pourquoi, sans révéler de détails internes du serveur.
Ne traitez pas un fichier directement depuis un chemin contrôlé par la requête et ne supposez pas qu’un téléversement interrompu est inoffensif. Les fichiers temporaires incomplets doivent être nettoyés et les nouvelles tentatives doivent être idempotentes lorsque le produit les prend en charge. Un jeton d’opération ou une clé d’idempotence évite de créer plusieurs pièces jointes équivalentes après des renvois réseau.
Stockage privé, quarantaine et traitement
Les binaires ne doivent pas se trouver sous le répertoire public de l’application. Stockez-les dans un stockage privé, avec des identifiants d’accès à privilèges minimaux, et associez chaque objet à un identifiant interne généré par le système. La base de données peut conserver des métadonnées telles que le propriétaire, le contexte métier, le type détecté, la taille, l’empreinte cryptographique, l’état, les dates et la politique de conservation. Évitez de stocker des données personnelles inutiles dans le nom ou dans les journaux opérationnels.
Un flux robuste sépare la réception et la disponibilité :
- L’application reçoit le fichier et crée un enregistrement à l’état
en_attenteouen_quarantaine. - Le binaire est déposé dans un emplacement non accessible aux téléchargements.
- Un processus asynchrone réalise une analyse antimalware, une validation approfondie, une transformation ou une extraction autorisée.
- Le résultat passe à
disponible,rejeteounecessite_revision. - L’interface affiche l’état opérationnel sans faire croire qu’un téléversement est déjà utilisable.
Les files d’attente réduisent le temps de réponse, mais introduisent leurs propres défaillances : tâches dupliquées, messages retardés et processeurs indisponibles. Concevez des tâches idempotentes, des limites du nombre de réessais, des alertes pour les éléments bloqués et une voie de retraitement contrôlée. Si l’analyse n’est pas disponible, l’alternative sûre consiste généralement à maintenir la pièce jointe en quarantaine, et non à la publier.
Autoriser chaque téléchargement et fournir le contenu sans l’exécuter
Le fait qu’une personne connaisse un identifiant ne lui accorde pas l’accès. Chaque téléchargement doit vérifier l’identité authentifiée, sa relation actuelle avec la ressource et le contexte métier : appartenance à une organisation, affectation au dossier, rôle en vigueur, état du document et restrictions temporelles. Ne réutilisez pas l’autorisation qui existait lors du téléversement du fichier ; les permissions peuvent avoir été révoquées depuis.
Le téléchargement doit passer par un contrôleur qui applique cette décision avant de lire ou de déléguer l’objet. Les URL signées peuvent être utiles pour servir du contenu depuis un stockage externe, mais elles exigent une portée limitée, une courte durée d’expiration et des contrôles empêchant leur émission pour des ressources non autorisées.
Servez les types actifs avec prudence. Pour les documents qui ne sont pas destinés à être rendus dans le navigateur, utilisez Content-Disposition: attachment. Définissez le type de contenu à partir du type validé, et non de l’extension, puis ajoutez X-Content-Type-Options: nosniff. Un aperçu n’est pas un simple téléchargement intégré : il doit utiliser des représentations transformées, isoler le contenu potentiellement actif et éviter de révéler des chemins internes.
Audit, récupération et tests avant la production

Enregistrez les événements pertinents : création, remplacement, changement d’état, téléchargement, suppression, erreur d’analyse et modification des permissions. Le journal doit contenir l’acteur, le moment, la ressource et le résultat, mais n’a pas besoin de stocker le binaire ni des données sensibles dupliquées. Protégez ces événements contre les altérations et définissez qui peut les consulter.
La récupération exige de savoir ce qui se produit si le stockage, la base de données ou le processeur échoue. Ne marquez pas un document comme disponible tant que le binaire et ses métadonnées ne sont pas cohérents. Concevez des tâches de rapprochement pour détecter les enregistrements sans objet, les objets orphelins et les éléments qui restent trop longtemps en quarantaine.
Liste de contrôle de sortie
- Les formats autorisés répondent à un cas d’usage documenté et sont validés selon leur contenu.
- Des limites de taille, de quantité, de temps et d’expansion des fichiers compressés existent.
- Les noms d’origine ne déterminent ni les chemins ni les noms physiques.
- Les binaires sont stockés hors du répertoire public et passent par une quarantaine lorsque cela est nécessaire.
- Le téléchargement réévalue l’autorisation et n’expose pas de chemins internes.
- Les fichiers malformés, les doublons, les téléversements interrompus, les permissions révoquées et les défaillances de stockage sont testés.
- Une supervision des rejets, des tâches bloquées, des erreurs de livraison et de la capacité consommée est en place.
- Les politiques de conservation, de suppression et d’audit ont un responsable opérationnel.
Transformer un téléversement en un flux avec des états et des contrôles peut sembler plus coûteux que d’utiliser un répertoire de téléversements. Pourtant, cela réduit les décisions improvisées lorsqu’apparaissent un fichier suspect, une réclamation d’accès ou une interruption opérationnelle. Cette traçabilité fait partie du produit, et non d’un ajout ultérieur.



