# Des tarifs partenaires négociés exposés en clair via une route REST non protégée

> Un office de tourisme découvre que ses tarifs négociés avec des hébergeurs partenaires sont accessibles publiquement par simple appel à l'API REST. Retour sur l'incident.

- Auteur : WordPress Développement
- Publié le : 2021-07-16
- Mis à jour le : 2021-07-16
- Catégorie : Sécurité
- URL : https://www.wpmoderne.fr/securite/tarifs-negocies-route-rest-non-protegee/

## L’essentiel

- Un champ ACF exposé par défaut n'est jamais neutre
- permission_callback doit exister même sur des données qui semblent publiques
- Un tarif négocié n'a de valeur que s'il reste confidentiel

Quarante pour cent de remise partenaire, visible en clair dans une réponse JSON accessible sans aucune authentification : c'est ce qu'a découvert un office de tourisme en examinant, par curiosité, ce que retournait l'API REST de son propre site WordPress sur le point de terminaison listant ses hébergements partenaires. La conception de cette API elle-même, pensée à l'origine pour alimenter une application mobile de recherche de logements, ne fait pas l'objet de cet article : le problème ici tient à un champ de métadonnées qui n'aurait jamais dû s'y trouver.

Les hébergements du territoire étaient enregistrés comme un type de contenu personnalisé, avec des champs personnalisés gérés via Advanced Custom Fields : nom, adresse, photos, mais aussi un champ interne `tarif_negocie_office`, utilisé uniquement pour le calcul de commissions dans le back-office, jamais destiné à un affichage public. Le problème : ce champ, comme tous les champs ACF associés au type de contenu, était automatiquement exposé dans la réponse de l'API REST par défaut, dès lors que le type de contenu avait `show_in_rest` activé pour alimenter l'application mobile.

## Pourquoi show_in_rest expose plus qu'on ne le pense

Activer `show_in_rest` sur un type de contenu personnalisé rend ses champs natifs accessibles via `/wp-json/wp/v2/hebergements`. Si ce type de contenu intègre également des champs ACF avec l'option « Afficher dans l'API REST » cochée par commodité lors de la création du champ, chacun de ces champs se retrouve dans la réponse JSON publique, sans distinction entre un champ destiné à l'affichage et un champ purement interne.

## Ce que révélait un simple appel à l'API

> L'essentiel à retenir : Un champ ACF exposé par défaut n'est jamais neutre ; permission_callback doit exister même sur des données qui semblent publiques ; Un tarif négocié n'a de valeur que s'il reste confidentiel

```
curl https://exemple-office-tourisme.fr/wp-json/wp/v2/hebergements/842
```

La réponse contenait, entre les champs attendus, la ligne suivante :

```
"acf": {
  "nom_hebergement": "Le Clos des Vignes",
  "tarif_negocie_office": "40%"
}
```

N'importe quel concurrent, ou l'hébergeur partenaire d'un autre établissement, pouvait consulter ce taux de remise simplement en connaissant l'identifiant du contenu, obtenu par un parcours trivial de la liste publique des hébergements.

## Corriger : retirer le champ de la réponse REST sans le supprimer du back-office

La correction ne consistait pas à supprimer le champ, toujours nécessaire en interne, mais à l'exclure explicitement de la réponse REST, via un filtre sur la préparation de la réponse :

```
add_filter( 'rest_prepare_hebergement', function( $response, $post, $request ) {
    $donnees = $response->get_data();
    unset( $donnees['acf']['tarif_negocie_office'] );
    $response->set_data( $donnees );
    return $response;
}, 10, 3 );
```

## Ajouter un permission_callback même sur des données qui paraissent publiques

Au-delà de ce champ précis, l'audit a révélé une lacune plus générale : la route REST personnalisée créée pour l'application mobile ne définissait aucun `permission_callback`, se contentant de la valeur par défaut, ouverte à tous. Même quand la majorité des données affichées sont effectivement publiques (nom, adresse, photos), l'absence de `permission_callback` explicite empêche toute évolution future du contenu de la route sans revoir chaque champ un par un :

```
register_rest_route( 'office/v1', '/hebergements', array(
    'methods'  => 'GET',
    'callback' => 'office_get_hebergements',
    'permission_callback' => '__return_true',
) );
```

Déclarer explicitement `__return_true` plutôt que de laisser le paramètre absent rend l'intention lisible et vérifiable lors d'un futur audit, plutôt que de laisser planer un doute sur un oubli.

> Un champ de métadonnées interne ne devient jamais public par accident sans raison : chaque champ ACF exposé à l'API REST mérite d'être validé un par un, jamais activé en bloc par confort.

## Notre verdict

Le taux de remise négocié n'a de valeur que tant qu'il reste confidentiel entre l'office de tourisme et l'hébergeur. L'exposer par un simple oubli de configuration ACF, sans même un accès authentifié requis, aurait pu compromettre des mois de négociations commerciales. Le correctif tient en quelques lignes ; l'audit qui l'a révélé, lui, a pris une matinée.
