Une case cochée « identité vérifiée » suffit-elle à prouver qu’un contrôle a réellement eu lieu ? Sur un formulaire de prise de rendez-vous destiné à des actes nécessitant une vérification préalable, la réponse dépend entièrement de l’endroit où cette vérification s’exécute. Si elle vit uniquement dans le navigateur, elle n’existe pas.
C’est ce qu’a révélé l’audit d’un cabinet notarial équipé d’un module WordPress personnalisé pour la prise de rendez-vous. Le formulaire imposait un téléversement de pièce d’identité avant de proposer des créneaux, avec un champ caché identity_checked positionné à 1 une fois le document accepté par un script de contrôle exécuté… en JavaScript, dans le navigateur du visiteur.
Un contrôle qui ne quitte jamais le poste client
Le script vérifiait la présence d’un fichier, son extension et sa taille, puis activait un bouton « Confirmer » et remplissait un champ masqué transmis avec le reste du formulaire. Rien de plus. Aucune route REST ni aucun traitement PHP ne revalidait cette information à la réception : le contrôleur du formulaire lisait directement la valeur de identity_checked et l’enregistrait telle quelle en tant que méta de la demande de rendez-vous.
Un outil comme les outils de développement du navigateur suffit à modifier cette valeur avant l’envoi. Pas besoin de compétences offensives particulières : ouvrir l’inspecteur, éditer l’attribut value de l’élément caché, soumettre. Le serveur, de son côté, n’a jamais eu la moindre preuve qu’un document avait été fourni.
Pourquoi ce défaut passe inaperçu en recette
Le parcours fonctionnel classique — remplir, téléverser, valider — masque le problème parce qu’il suit toujours le chemin prévu. Les tests manuels reproduisent le comportement attendu de l’interface, jamais une manipulation directe de la requête HTTP sortante. Ce type de faille ne se voit qu’en observant la charge utile réellement transmise au serveur, indépendamment de ce que l’écran affiche.
- Le champ caché portait un nom explicite, ce qui facilitait sa localisation pour un attaquant curieux.
- Aucune valeur de contrôle n’était liée à une session ou à un jeton unique côté serveur.
- Le traitement PHP faisait une confiance totale à toute donnée présente dans
$_POST.

Reconstruire la vérification côté serveur
La correction n’ajoute pas de complexité disproportionnée : elle déplace simplement le contrôle. Le fichier téléversé doit être analysé côté PHP au moment de la soumission, avec un identifiant de vérification généré et stocké côté serveur, jamais fourni ni modifiable par le client.
function cabinet_verifier_upload_identite( $request ) {
$fichiers = $request->get_file_params();
if ( empty( $fichiers['piece_identite'] ) ) {
return new WP_Error( 'identite_absente', 'Aucune pièce jointe reçue.', array( 'status' => 400 ) );
}
$type = wp_check_filetype( $fichiers['piece_identite']['name'] );
if ( ! in_array( $type['ext'], array( 'jpg', 'jpeg', 'png', 'pdf' ), true ) ) {
return new WP_Error( 'format_invalide', 'Format de document non accepté.', array( 'status' => 400 ) );
}
// Le jeton de vérification est généré ici, jamais transmis par le client.
$jeton = wp_generate_password( 32, false );
set_transient( 'rdv_identite_' . $jeton, true, HOUR_IN_SECONDS );
return array( 'jeton_verification' => $jeton );
}
Ce jeton, à usage unique et à durée de vie limitée, devient la seule preuve acceptée à l’étape suivante du formulaire. Le champ caché disparaît du modèle de confiance : il peut rester dans l’interface pour l’ergonomie, mais plus aucun traitement serveur ne s’y fie.
Ce que la fonction current_user_can ne résout pas ici
Il est tentant de penser qu’une vérification de capacité règle ce genre de problème. Ce n’est pas le cas dans ce contexte précis : le visiteur qui prend rendez-vous n’est généralement pas authentifié, donc current_user_can n’entre pas en jeu. La discipline à appliquer relève plutôt de la validation systématique de toute donnée transmise par un client non authentifié, y compris et surtout celles qui semblent techniques ou internes.
Règle maison retenue après cet audit : toute variable qui conditionne une décision métier doit pouvoir survivre à un client qui ment. Si elle ne le peut pas, elle ne doit jamais quitter le serveur.
Étendre le contrôle à d’autres formulaires sensibles
Le cabinet exploitait deux autres formulaires construits sur le même schéma technique : une demande de procuration et un dépôt de dossier de succession. Les trois partageaient la même faiblesse, preuve qu’un défaut de conception se propage silencieusement dès lors qu’un composant est dupliqué sans revue.
| Formulaire | Contrôle avant correctif | Contrôle après correctif |
|---|---|---|
| Prise de rendez-vous | Champ caché côté client | Jeton serveur à usage unique |
| Demande de procuration | Champ caché côté client | Jeton serveur à usage unique |
| Dépôt de dossier de succession | Aucun contrôle explicite | Validation de type et jeton serveur |
Notre verdict
Un formulaire qui touche à une exigence légale ou contractuelle de vérification d’identité ne peut jamais déléguer cette vérification au navigateur, même partiellement. La règle est simple à énoncer et pourtant régulièrement oubliée sous la pression du calendrier : toute logique de confiance doit se rejouer intégralement côté serveur, sans exception pour les champs qui paraissent techniques. Ici, corriger les trois formulaires a demandé moins d’une journée ; les repérer en a demandé beaucoup plus.