Un retour chariot (\r\n) glissé dans un champ « Nom » d’un formulaire de contact : deux caractères invisibles à l’écran, mais capables, sur une intégration mal protégée, d’ajouter une ligne Bcc: arbitraire à l’e-mail généré automatiquement par l’API HubSpot lors de la soumission du formulaire. C’est le principe classique de l’injection d’en-têtes e-mail, connu depuis longtemps mais toujours exploitable dès qu’un champ de formulaire est transmis sans validation stricte à un service qui génère un e-mail à partir de son contenu.
Ce type de faille a été identifié sur un formulaire de demande de devis, où le champ « Nom de l’entreprise » était directement inséré dans l’objet de l’e-mail de notification généré par HubSpot, sans qu’aucune validation ne restreigne les caractères autorisés dans ce champ.
Comprendre le mécanisme de l’injection
La plupart des systèmes de génération d’e-mails construisent leurs en-têtes en concaténant des champs fournis par l’utilisateur avec des libellés fixes, par exemple Subject: Nouvelle demande de {entreprise}. Si le champ {entreprise} contient un retour à la ligne suivi d’un nouvel en-tête (\r\nBcc: attaquant@exemple.com), et que le système de génération ne filtre pas ces caractères avant assemblage, l’en-tête injecté devient un en-tête réel du message final, avec toutes les conséquences que cela implique : copie cachée vers une adresse arbitraire, modification du destinataire, voire dans certains cas ajout de corps de message supplémentaire.
HubSpot filtre une partie de ces cas côté plateforme, mais un champ personnalisé injecté dans un template d’e-mail transactionnel configuré manuellement peut échapper à ce filtrage selon la façon dont le template a été construit, ce qui a été précisément le cas sur ce projet.

Le correctif : valider le format attendu, pas seulement la présence
La correction distingue deux niveaux de validation trop souvent confondus : vérifier qu’un champ n’est pas vide ne garantit rien sur son contenu. La validation doit porter sur le format réellement attendu pour chaque champ, en rejetant explicitement tout caractère de contrôle avant transmission à l’API :
function valider_champ_formulaire($valeur, $type_champ) {
// Rejet systématique des caractères de contrôle et retours à la ligne
if (preg_match('/[\r\n\x00-\x1F]/', $valeur)) {
return new WP_Error(
'caractere_invalide',
'Ce champ ne peut pas contenir de saut de ligne.'
);
}
switch ($type_champ) {
case 'nom_entreprise':
if (!preg_match('/^[\p{L}0-9\s\-\'&.]{1,120}$/u', $valeur)) {
return new WP_Error('format_invalide', 'Nom d\'entreprise invalide.');
}
break;
case 'email':
if (!is_email($valeur)) {
return new WP_Error('email_invalide', 'Adresse e-mail invalide.');
}
break;
}
return sanitize_text_field($valeur);
}
La fonction WordPress sanitize_text_field() supprime déjà une partie des caractères de contrôle et des retours à la ligne, mais s’appuyer uniquement sur elle sans validation de format explicite laisse passer des valeurs syntaxiquement valides tout en étant sémantiquement inattendues pour le champ concerné, comme un nom d’entreprise contenant une adresse e-mail complète ou une URL.
Traiter aussi le côté template HubSpot
Au-delà de la validation côté WordPress, le template d’e-mail transactionnel configuré dans HubSpot a été révisé pour ne plus insérer directement de champ personnalisé dans l’en-tête Objet du message : la personnalisation se limite désormais au corps de l’e-mail, où une injection d’en-tête n’a plus aucun effet, un retour à la ligne dans le corps du message restant un simple retour à la ligne, sans conséquence sur la structure des en-têtes.
Tester la correction
Le test de non-régression consiste à soumettre volontairement le formulaire avec un champ contenant une séquence \r\nBcc: encodée, en vérifiant à la fois que la requête est rejetée côté WordPress avant tout envoi vers HubSpot, et que l’e-mail final, en cas de contournement de cette première couche, ne contient aucun en-tête supplémentaire par rapport à un envoi normal.
En résumé
Un champ de formulaire destiné à alimenter un e-mail automatique mérite une validation aussi stricte qu’un champ destiné à une requête SQL : le format attendu doit être défini explicitement, et tout caractère de contrôle rejeté avant transmission à l’API tierce, plutôt que de faire confiance au filtrage éventuel du service d’emailing en bout de chaîne.