vendredi 25 septembre 2026

À propos

Contact

Sécurité

Sécuriser les téléversements REST : le champ oublié des extensions headless

Une route REST personnalisée qui accepte des fichiers doit reproduire les contrôles de wp_handle_upload, ce que beaucoup de développeurs headless négligent. Checklist des vérifications.

Par Clément Hadrot • 25 septembre 2022 • 5 min de lecture • Aucun commentaire
Sécuriser les téléversements REST : le champ oublié des extensions headless

Un projet headless pour un client media construit son front-end en Next.js et communique avec WordPress exclusivement via l’API REST, y compris pour permettre aux journalistes de téléverser des images depuis l’interface de rédaction externe. Le développeur en charge du point d’entrée REST personnalisé, plus habitué aux frameworks JavaScript qu’à l’écosystème WordPress, a écrit une route fonctionnelle en quelques lignes — sans savoir que WordPress applique normalement, lors d’un upload classique via l’administration, une série de vérifications qu’aucune route REST personnalisée n’hérite automatiquement.

C’est un piège récurrent dans les projets headless : l’équipe front-end maîtrise parfaitement son framework, l’équipe back-end connaît WordPress, mais la frontière entre les deux — une route REST personnalisée qui gère un upload — se retrouve parfois écrite par la personne la moins familière avec les protections que WordPress fournit ailleurs, et qui ignore donc qu’il faut les reproduire explicitement.

Ce que wp_handle_upload fait, sans qu’on y pense

Quand un fichier est envoyé depuis l’administration WordPress classique (médiathèque, champ d’upload d’un formulaire natif), il transite par wp_handle_upload(), une fonction qui regroupe silencieusement plusieurs contrôles : vérification du type MIME réel via wp_check_filetype_and_ext(), restriction aux extensions autorisées par le filtre upload_mimes, génération d’un nom de fichier unique et assaini, et exécution des hooks de sécurité qu’une extension de protection (antivirus, pare-feu applicatif) aurait pu accrocher sur ce point d’entrée standard.

Une route REST personnalisée qui manipule $_FILES directement, sans jamais appeler wp_handle_upload(), ne bénéficie d’aucune de ces protections — même si l’extension WordPress installée par ailleurs promet une protection générale des uploads, puisque cette protection est souvent accrochée précisément sur ce point d’entrée standard qui, ici, n’est jamais sollicité.

La checklist des quatre contrôles à reproduire

L'essentiel à retenir : register_rest_route n'applique aucun contrôle d'upload par défaut ; wp_handle_upload regroupe des vérifications qu'il faut reproduire ; Une checklist avant d'ouvrir une route d'upload personnalisée
  1. Appeler wp_handle_upload() plutôt que de traiter $_FILES à la main, pour hériter automatiquement de l’ensemble des vérifications natives plutôt que de les réinventer partiellement :
    add_action( 'rest_api_init', function() {
        register_rest_route( 'redaction/v1', '/upload-image', array(
            'methods'             => 'POST',
            'callback'            => 'moncpt_upload_image_redaction',
            'permission_callback' => function() {
                return current_user_can( 'upload_files' );
            },
        ) );
    } );
    
    function moncpt_upload_image_redaction( WP_REST_Request $request ) {
        if ( empty( $_FILES['image'] ) ) {
            return new WP_Error( 'fichier_manquant', 'Aucun fichier reçu.', array( 'status' => 400 ) );
        }
    
        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';
    
        $resultat = wp_handle_upload( $_FILES['image'], array( 'test_form' => false ) );
    
        if ( isset( $resultat['error'] ) ) {
            return new WP_Error( 'upload_refuse', $resultat['error'], array( 'status' => 400 ) );
        }
    
        return new WP_REST_Response( array( 'url' => $resultat['url'] ), 201 );
    }
  2. Ne jamais omettre permission_callback, en vérifiant une capacité réellement adaptée à l’upload (upload_files), jamais un simple __return_true laissé « pour tester » et oublié en production — une négligence malheureusement fréquente sur les routes développées dans l’urgence.
  3. Limiter explicitement la taille acceptée avant même d’appeler wp_handle_upload(), indépendamment de la limite globale upload_max_filesize du serveur, pour éviter qu’un point d’entrée dédié à des photos de quelques mégaoctets n’accepte silencieusement un fichier de plusieurs centaines de mégaoctets et sature l’espace disque ou la mémoire du traitement d’image :
    if ( $_FILES['image']['size'] > 8 * MB_IN_BYTES ) {
        return new WP_Error( 'fichier_trop_volumineux', 'Image limitée à 8 Mo.', array( 'status' => 413 ) );
    }
  4. Restreindre la liste des types MIME acceptés à ce qui est réellement utile pour ce point d’entrée précis, via le filtre upload_mimes appliqué localement à cette route plutôt que globalement à tout le site, pour éviter qu’un point d’entrée pensé pour des photos n’accepte aussi, par héritage de la configuration générale, des formats sans rapport avec l’usage (archives, documents exécutables sur certains systèmes) :
    add_filter( 'upload_mimes', function( $mimes ) {
        return array(
            'jpg|jpeg' => 'image/jpeg',
            'png'      => 'image/png',
            'webp'     => 'image/webp',
        );
    }, 10, 1 );
    // À restreindre au contexte de cette route précise via une vérification
    // de l'action REST en cours, pour ne pas affecter le reste du site.

Un test simple pour vérifier qu’une route hérite bien de ces protections

Une fois la route corrigée, un test rapide avec curl confirme le comportement attendu, en tentant volontairement d’envoyer un fichier PHP renommé en .jpg, exactement le type de test qui aurait révélé la faille avant sa mise en production :

curl -X POST "https://exemple.fr/wp-json/redaction/v1/upload-image" \
  -H "Authorization: Bearer TOKEN_DE_TEST" \
  -F "image=@webshell-deguise.php.jpg;type=image/jpeg"
# Doit renvoyer une erreur explicite, jamais un succès avec une URL de fichier

Sur les projets headless que nous auditons, la première question posée à propos de toute route d’upload personnalisée est invariablement la même : « appelle-t-elle wp_handle_upload(), ou traite-t-elle $_FILES à la main ? ». La seconde réponse déclenche systématiquement une revue approfondie.

Ce que cette checklist ne couvre pas

Ce sujet ne traite pas de la sécurisation de l’authentification des routes REST elle-même (jetons d’application, JWT, OAuth), un préalable indispensable mais distinct, traité par ailleurs. Une route d’upload parfaitement protégée sur le plan des fichiers acceptés reste inutile si l’authentification qui la protège en amont est elle-même mal conçue.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi