Le WordPress d'aujourd'hui, décodé pour les développeurs

Sécurité

Un annuaire associatif de 100 000 fiches : l’énumération de contacts

Une API REST personnalisée d'un annuaire fédérant des associations exposait par erreur des champs de contact non publics. Retour sur la découverte et la correction par capacité et par champ.

Par WordPress Développement • 26 décembre 2021 • 4 min de lecture • Aucun commentaire
Un annuaire associatif de 100 000 fiches : l'énumération de contacts

Cent mille fiches : c’est la taille de l’annuaire construit par une fédération nationale d’associations, chaque fiche décrivant une structure locale avec ses coordonnées publiques et, pour certaines, les informations de contact de bénévoles n’ayant jamais souhaité les voir publiées. Le site s’appuyait sur un type de contenu personnalisé WordPress, exposé via l’API REST pour alimenter un front-end de recherche géographique développé séparément.

Le problème n’a pas été détecté par un audit planifié, mais par un développeur externe qui, en explorant la structure de l’API pour construire une nouvelle fonctionnalité, a remarqué que la réponse JSON d’une fiche contenait bien plus de champs que ceux affichés sur le site public.

Ce que l’exploration de l’API a révélé

En interrogeant simplement /wp-json/wp/v2/associations/{id} sur un identifiant quelconque, la réponse incluait, en plus des champs publics attendus (nom, ville, description), plusieurs champs personnalisés destinés à un usage strictement interne : l’adresse e-mail personnelle du référent bénévole, son numéro de téléphone, et un champ de notes internes utilisé par l’équipe de la fédération pour suivre les échanges avec chaque structure locale.

En parcourant l’ensemble des cent mille fiches via l’endpoint de collection paginé, ces champs devenaient accessibles en masse, sans authentification, à quiconque savait interroger l’API REST de WordPress, ce qui ne demande aucune compétence particulière au-delà de la lecture de la documentation officielle du projet.

Pourquoi ces champs étaient exposés

Les champs personnalisés avaient été déclarés avec register_rest_field lors du développement initial du type de contenu, à une époque où seul un usage interne de l’API était prévu, avant l’ajout ultérieur du front-end de recherche public. Le paramètre show_in_rest avait été activé globalement sur l’ensemble des champs du type de contenu par commodité de développement, sans distinction entre les champs destinés à un usage public et ceux réservés à l’équipe interne.

Cette exposition n’avait jamais été identifiée comme un problème parce que le front-end du site n’affichait que les champs publics : le défaut restait invisible tant qu’on se contentait de consulter le site via son interface prévue, et ne devenait visible qu’en interrogeant directement l’API.

L'essentiel à retenir : Un champ show_in_rest coché par facilité expose sans distinction ; Le contrôle doit s'appliquer champ par champ, pas seulement route par route ; L'énumération massive révèle des défauts invisibles à l'échelle d'une fiche

Le correctif : un contrôle d’accès par champ, pas par route

La correction a consisté à retirer l’exposition REST globale et à redéclarer chaque champ individuellement, avec une fonction de vérification de capacité (permission_callback) propre à chaque champ sensible, plutôt qu’un seul contrôle au niveau de la route :

register_rest_field('association', 'contact_interne_email', [
    'get_callback' => function ($object) {
        if (!current_user_can('manage_options')) {
            return null;
        }
        return get_post_meta($object['id'], 'contact_interne_email', true);
    },
    'schema' => [
        'type'    => 'string',
        'context' => ['edit'],
    ],
]);

register_rest_field('association', 'nom_public', [
    'get_callback' => function ($object) {
        return get_post_meta($object['id'], 'nom_public', true);
    },
    'schema' => ['type' => 'string', 'context' => ['view', 'edit']],
]);

Le paramètre context joue ici un rôle clé : en limitant certains champs au contexte edit, ils n’apparaissent que dans les requêtes authentifiées avec les droits d’édition, jamais dans le contexte view utilisé par les visiteurs anonymes du front-end.

Une revue systématique après correction

Au-delà du correctif immédiat, l’équipe a mené un audit complet de tous les types de contenu personnalisés du site, en listant chaque champ exposé via show_in_rest et en le classant explicitement comme public, interne, ou à supprimer de l’API si aucun usage front-end ne le justifiait. Cette revue a permis de découvrir deux autres champs sensibles similaires sur un type de contenu distinct, gérant les demandes de subvention des structures locales.

En résumé

À l’échelle d’une fiche unique, ce type de défaut passe totalement inaperçu : rien ne le distingue visuellement d’un fonctionnement normal. C’est l’échelle de cent mille fiches interrogeables en masse qui transforme un champ mal configuré en fuite de données personnelles significative. La leçon à retenir dépasse ce projet précis : chaque champ personnalisé exposé via l’API REST mérite une décision explicite sur sa visibilité, jamais une activation par défaut appliquée sans réflexion à l’ensemble d’un type de contenu.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Partager :

À propos de l'auteur

WordPress Développement

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

Voir tous ses articles

Dans la même veine

À lire aussi