# Deviner l’identifiant du client suivant sur un simulateur d’assurance

> Changer un seul chiffre dans une URL de devis suffisait à afficher celui d'un autre client, sans authentification d'aucune sorte.

- Auteur : WordPress Développement
- Publié le : 2023-05-19
- Mis à jour le : 2023-05-19
- Catégorie : Sécurité
- URL : https://www.wpmoderne.fr/securite/deviner-identifiant-client-suivant-simulateur-assurance/

## L’essentiel

- Un identifiant séquentiel se devine par simple incrémentation
- L'absence de contrôle d'appartenance transforme un identifiant en clé universelle
- Un identifiant opaque ralentit l'attaque sans remplacer le contrôle d'accès

`?devis=4821` devenu `?devis=4822` par simple curiosité dans la barre d'adresse : un seul chiffre changé dans l'URL du simulateur en ligne d'un cabinet d'assurance suffisait à afficher intégralement le devis d'un autre client, coordonnées et montants compris, sans la moindre demande d'authentification.

Le simulateur, développé comme extension WordPress sur mesure, calculait une estimation de prime à partir d'un formulaire en plusieurs étapes, puis enregistrait chaque résultat dans une table personnalisée avec un identifiant auto-incrémenté classique, exposé tel quel dans l'URL de la page de résultat.

## Un identifiant conçu pour la base, exposé au public

La clé primaire auto-incrémentée d'une table SQL répond à un besoin d'unicité et de performance interne. Elle n'a jamais été pensée comme un secret ni comme un droit d'accès : sa prévisibilité même est une propriété recherchée pour l'indexation. Le simulateur reprenait pourtant cette même valeur, sans transformation, comme unique paramètre de la page de résultat.

```
function afficher_resultat_devis() {
    $id = isset( $_GET['devis'] ) ? absint( $_GET['devis'] ) : 0;
    global $wpdb;
    $devis = $wpdb->get_row(
        $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}devis_assurance WHERE id = %d", $id )
    );
    // Aucune vérification que $devis appartient au visiteur courant.
    return generer_html_devis( $devis );
}
```

La requête elle-même est correctement protégée contre l'injection SQL grâce à `$wpdb->prepare()`. Le problème ne se situe pas là : il se situe dans l'absence totale de contrôle sur le droit du visiteur à consulter précisément cette ligne, quelle qu'elle soit.

## Comment l'énumération a été mise en évidence

Lors de l'audit, une simple boucle de test, exécutée sur un environnement de recette autorisé, a permis de récupérer plusieurs centaines de devis en quelques minutes en faisant varier l'identifiant de façon séquentielle. Aucun mécanisme de limitation de débit ni de détection d'énumération n'existait sur cette route, qui n'était d'ailleurs même pas déclarée comme une route de l'API REST : il s'agissait d'un simple gabarit de page WordPress recevant un paramètre `GET`.

- L'identifiant exposé correspondait exactement à la clé primaire de la table SQL.
- Aucune vérification d'appartenance ne comparait le devis demandé à une session ou un compte.
- Aucune limitation de fréquence ne ralentissait une énumération automatisée.

> L'essentiel à retenir : Un identifiant séquentiel se devine par simple incrémentation ; L'absence de contrôle d'appartenance transforme un identifiant en clé universelle ; Un identifiant opaque ralentit l'attaque sans remplacer le contrôle d'accès

## Deux niveaux de correction, pas un seul

La tentation la plus fréquente consiste à remplacer l'identifiant séquentiel par un identifiant opaque, généré aléatoirement. C'est une amélioration réelle, mais partielle : elle rend l'énumération beaucoup plus difficile, elle ne rétablit pas un contrôle d'accès qui n'existait pas. Un identifiant opaque intercepté ou partagé par erreur resterait tout aussi consultable par n'importe qui.

```
function afficher_resultat_devis() {
    $jeton = isset( $_GET['devis'] ) ? sanitize_text_field( $_GET['devis'] ) : '';
    global $wpdb;
    $devis = $wpdb->get_row(
        $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}devis_assurance WHERE jeton_acces = %s AND session_id = %s",
            $jeton,
            wp_get_session_token()
        )
    );
    return $devis ? generer_html_devis( $devis ) : afficher_erreur_acces();
}
```

Le correctif retenu combine les deux niveaux : un jeton d'accès opaque, généré via `wp_generate_password()`, remplace l'identifiant séquentiel dans l'URL, et une vérification d'appartenance liée à la session du visiteur conditionne systématiquement l'affichage.

## Un défaut fréquent au-delà de ce seul simulateur

Cette classe de vulnérabilité, connue sous le nom de référence d'objet directe non sécurisée, touche fréquemment les fonctionnalités développées rapidement pour un besoin métier ponctuel : simulateur, générateur de PDF, page de confirmation de commande. Le développement se concentre sur le calcul métier, la question de l'accès arrive après, parfois jamais.

| Approche | Empêche l'énumération | Empêche l'accès non autorisé |
| --- | --- | --- |
| Identifiant séquentiel exposé | Non | Non |
| Identifiant opaque seul | Oui, en pratique | Non |
| Identifiant opaque + contrôle d'appartenance | Oui | Oui |

> Conseil retenu de cet audit : un identifiant difficile à deviner reste un identifiant, pas un contrôle d'accès. Les deux se complètent, ils ne se remplacent jamais l'un l'autre.

## Notre verdict

Le cabinet a choisi de conserver la logique de calcul du simulateur intacte et de ne toucher qu'à la couche d'accès aux résultats, preuve qu'une telle correction ne demande pas de remettre en cause l'architecture existante. Elle demande simplement de poser la question qui manquait à l'origine : cette page appartient-elle réellement à la personne qui la consulte ?
