Un client demande un formulaire permettant à ses utilisateurs d’envoyer un justificatif, une photo de profil ou un document PDF. La demande semble anodine, mais un champ d’upload mal sécurisé dans une extension maison figure parmi les vecteurs de compromission les plus fréquents que nous rencontrons en audit, souvent bien plus dangereux qu’une simple faille XSS.
Le problème ne vient presque jamais de l’absence totale de vérification — la plupart des développeurs pensent au moins à contrôler l’extension du fichier. Il vient de vérifications incomplètes qui donnent une fausse impression de sécurité. Voici la démarche complète, étape par étape, pour traiter un upload correctement dans une extension WordPress.
Étape 1 : ne jamais faire confiance à l’extension déclarée seule
Un fichier nommé photo.jpg peut très bien contenir du code PHP : rien n’empêche un attaquant de renommer un script malveillant avant de l’envoyer. Vérifier uniquement l’extension du nom de fichier, via une simple comparaison de chaîne, ne protège donc de rien face à un attaquant un minimum informé.
WordPress fournit une fonction native conçue précisément pour ce cas : wp_check_filetype_and_ext(). Elle croise l’extension déclarée avec le type MIME réellement détecté à partir du contenu du fichier, et rejette toute incohérence.
$verification = wp_check_filetype_and_ext(
$_FILES['justificatif']['tmp_name'],
$_FILES['justificatif']['name']
);
if ( empty( $verification['ext'] ) || empty( $verification['type'] ) ) {
wp_die( __( 'Type de fichier non autorisé.', 'mon-extension' ) );
}
Étape 2 : restreindre à une liste blanche stricte de types MIME
Même correctement détecté, un type MIME ne doit être accepté que s’il figure explicitement dans la liste des formats attendus pour ce formulaire précis. Accepter « tout ce qui est une image » est déjà trop large si seul le JPEG est réellement utile :
$types_autorises = array(
'jpg|jpeg' => 'image/jpeg',
'png' => 'image/png',
'pdf' => 'application/pdf',
);
$verification = wp_check_filetype_and_ext(
$_FILES['justificatif']['tmp_name'],
$_FILES['justificatif']['name'],
$types_autorises
);

Étape 3 : passer par wp_handle_upload plutôt que réinventer le stockage
Une fois le fichier validé, la fonction wp_handle_upload() gère le déplacement vers un emplacement final, génère un nom de fichier sûr et évite les collisions. Elle attend un tableau de type $_FILES et un jeu d’options :
require_once ABSPATH . 'wp-admin/includes/file.php';
$resultat = wp_handle_upload(
$_FILES['justificatif'],
array( 'test_form' => false, 'mimes' => $types_autorises )
);
if ( isset( $resultat['error'] ) ) {
wp_die( esc_html( $resultat['error'] ) );
}
Réécrire soi-même la logique de déplacement de fichier avec move_uploaded_file() brut est possible, mais chaque détail oublié (nom de fichier non assaini, absence de vérification d’erreur d’upload) réintroduit un risque que wp_handle_upload() gère déjà.
Étape 4 : stocker les fichiers sensibles hors de portée directe
Pour des documents qui ne doivent pas être accessibles publiquement par simple connaissance de l’URL (justificatifs d’identité, factures), le dossier wp-content/uploads standard n’est pas adapté : tout fichier qui y est déposé devient accessible à quiconque devine ou trouve son chemin.
- Stocker ces fichiers dans un répertoire dédié, en dehors du chemin racine servi par le serveur web.
- Servir leur contenu via un script PHP qui vérifie les droits de l’utilisateur avant de le transmettre (avec un en-tête
Content-Dispositionadapté). - Combiner cette approche avec la règle qui interdit l’exécution de PHP dans le dossier d’upload, en défense supplémentaire.
Ce qu’il ne faut jamais faire
Un formulaire d’upload qui fait confiance à l’extension du nom de fichier fourni par le navigateur, c’est comme vérifier l’identité de quelqu’un en lui demandant simplement de se présenter.
Se fier au type MIME envoyé par le navigateur dans $_FILES['champ']['type'] relève de la même erreur : cette valeur est entièrement contrôlée par le client et se falsifie sans effort. Seule la détection basée sur le contenu réel du fichier, via wp_check_filetype_and_ext(), mérite d’être considérée comme fiable côté serveur.
En résumé
Sécuriser un upload dans une extension WordPress tient en quatre réflexes : vérifier le contenu réel du fichier et pas seulement son nom, restreindre à une liste blanche de types précis, s’appuyer sur wp_handle_upload() plutôt que réinventer la logique de stockage, et isoler les documents sensibles hors de portée directe. Chacune de ces étapes ferme une porte que les attaquants automatisés testent en premier.