Un client du secteur associatif nous demande un audit avant une mise en conformité RGPD plus large. Le site propose un espace adhérent avec un formulaire d’inscription construit avec une extension de formulaires généraliste, incluant un champ « choisissez un mot de passe » pour un accès à un espace privé non lié aux comptes WordPress natifs — une fonctionnalité maison ajoutée par un précédent prestataire par-dessus l’extension. En inspectant la base de données à la recherche de champs sensibles, une requête simple sur wp_postmeta révèle le problème : plus de 1 200 entrées contenant une clé mot_de_passe_adherent avec, en valeur, des chaînes en clair, parfaitement lisibles.
Aucun hachage, aucun chiffrement : le mot de passe saisi par chaque adhérent lors de son inscription était stocké tel quel, comme n’importe quel autre champ de formulaire (nom, adresse, message). Le prestataire précédent avait traité un champ d’authentification exactement comme un champ de texte libre, sans distinction, probablement sans même s’en rendre compte tant l’extension traite tous les champs de façon générique par défaut.
Pourquoi ce piège est si facile à tomber dedans
Les extensions de formulaires généralistes (constructeurs de formulaires par glisser-déposer) sont conçues pour collecter des données arbitraires et les stocker de façon uniforme, généralement sous forme d’entrées liées à un article de type post dédié (souvent nommé {prefixe}_entry ou similaire), avec chaque champ enregistré en métadonnée. C’est un choix d’architecture parfaitement raisonnable pour des champs texte, e-mail ou message. Il devient dangereux dès qu’un champ de type password est ajouté au formulaire pour construire, par-dessus l’extension, un système d’authentification maison — sans que personne n’ajoute l’étape de hachage qu’un tel champ implique.
Le champ HTML <input type="password"> masque seulement la saisie à l’écran ; il ne modifie en rien la façon dont la valeur est transmise ou stockée côté serveur. C’est une confusion visuelle qui se traduit régulièrement en confusion de sécurité chez les équipes qui assemblent des formulaires sans expertise back-end dédiée.
Détecter le problème systématiquement

Avant de corriger quoi que ce soit, il faut mesurer l’ampleur réelle du problème : combien d’entrées sont concernées, depuis quand, et quels champs sont impliqués. Une requête directe sur la base, avant toute modification, permet ce diagnostic sans risque :
SELECT meta_key, COUNT(*) AS occurrences
FROM wp_postmeta
WHERE meta_key LIKE '%mot_de_passe%'
OR meta_key LIKE '%password%'
GROUP BY meta_key;
Cette recherche par motif de nom de clé est un point de départ, pas une garantie : un champ sensible peut être nommé n’importe comment par l’auteur du formulaire (code_acces, pin, secret). Sur un audit plus large, nous complétons par une recherche sur la forme des valeurs elles-mêmes plutôt que sur le nom des clés, en cherchant des motifs suspects (valeurs courtes, alphanumériques, associées à un champ e-mail dans la même entrée) — une méthode plus lourde mais qui rattrape les formulaires mal nommés.
Avec WP-CLI, un export ciblé permet de vérifier rapidement sans écrire de SQL brut :
wp post meta list --post_type=entry --keys=mot_de_passe_adherent --format=csv | head
Corriger sans perdre les comptes existants
La correction ne se limite pas à ajouter un hachage pour les nouvelles inscriptions : les 1 200 entrées existantes contiennent des mots de passe en clair qui doivent être neutralisées, pas simplement protégées à l’avenir. La démarche retenue avec ce client s’est faite en trois temps :
- Modifier immédiatement le traitement du formulaire pour hacher toute nouvelle valeur avec
wp_hash_password(), la fonction native de WordPress pour ce usage, avant tout enregistrement en base. - Migrer les entrées existantes en hachant leur valeur en clair actuelle, de façon à ce qu’un adhérent qui se reconnecte avec son mot de passe initial soit toujours reconnu, sans qu’il ait besoin de le réinitialiser.
- Notifier malgré tout l’ensemble des adhérents concernés de la nécessité de changer leur mot de passe, par précaution, puisque la valeur en clair a existé en base pendant une période non négligeable et a pu être exposée par ailleurs (sauvegardes, accès prestataire).
function moncpt_migrer_mots_de_passe_en_clair() {
$entrees = get_posts( array(
'post_type' => 'entry',
'posts_per_page' => -1,
'fields' => 'ids',
) );
foreach ( $entrees as $id ) {
$valeur = get_post_meta( $id, 'mot_de_passe_adherent', true );
// On ignore les valeurs déjà hachées (préfixe reconnaissable)
if ( empty( $valeur ) || 0 === strpos( $valeur, '$wp' ) ) {
continue;
}
update_post_meta(
$id,
'mot_de_passe_adherent',
wp_hash_password( $valeur )
);
}
}
Pour la vérification lors de la connexion à l’espace adhérent, la fonction miroir wp_check_password() compare une valeur en clair saisie au moment de la connexion avec le hachage stocké, sans jamais avoir besoin de déchiffrer quoi que ce soit — le hachage n’est volontairement pas réversible.
Ce que cet audit n’a pas couvert
Cette intervention portait sur un système d’authentification maison construit au-dessus d’une extension de formulaire générique, pas sur le mécanisme de hachage des mots de passe des comptes utilisateurs natifs de WordPress, qui utilise son propre circuit (wp_hash_password également, mais appelé par le cœur lors de l’inscription et de la connexion standard) et qui n’était, lui, pas en cause ici.
Sur nos audits, tout champ de formulaire portant le mot « mot de passe », « code » ou « pin » dans son libellé déclenche désormais une vérification systématique de son stockage réel en base, indépendamment de la confiance accordée à l’extension utilisée.
En résumé
Un champ visuellement masqué à l’écran ne dit rien de sa protection réelle en base de données. Toute donnée d’authentification ajoutée par-dessus une extension généraliste doit être traitée explicitement par du code applicatif dédié au hachage, jamais laissée au comportement par défaut, générique et non spécialisé, d’un constructeur de formulaires.