Le WordPress d'aujourd'hui, décodé pour les développeurs

Sécurité

Neutraliser les caractères Unicode pleine largeur qui contournent une liste noire de mots interdits

Un pseudonyme composé de caractères pleine largeur passe un filtre de mots interdits sans y ressembler à l'œil. Normaliser la chaîne avant comparaison referme cette brèche.

Par WordPress Développement • 14 septembre 2021 • 4 min de lecture • Aucun commentaire
Neutraliser les caractères Unicode pleine largeur qui contournent une liste noire de mots interdits

A et A : à l’écran, ces deux caractères se ressemblent au point d’être confondus dans une relecture rapide. Le premier appartient au bloc Unicode des « formes pleine largeur », conçu à l’origine pour l’affichage cohérent de texte latin aux côtés de caractères d’Asie de l’Est. Son point de code, 65 281, n’a rien à voir avec le 65 du « A » latin de base.

Le problème : une liste noire qui ne voit que des octets

Un filtre de modération qui compare un pseudonyme saisi à une liste de mots interdits, par exemple via str_contains() ou une expression régulière sur l’alphabet ASCII, ne détecte jamais une variante composée de caractères pleine largeur. Le mot interdit écrit avec ces caractères passe le filtre sans encombre, tout en restant lisible, voire identique en apparence, pour un modérateur humain qui consulterait la liste des pseudonymes après coup.

Ce contournement ne relève d’aucune faille technique complexe : il exploite simplement le fait que la table Unicode contient plusieurs façons d’encoder une apparence visuelle proche, alors qu’un filtre de mots interdits raisonne le plus souvent chaîne contre chaîne, sans tenir compte de cette diversité de représentation.

Le snippet : normaliser avant de comparer

La normalisation Unicode de type NFKC, disponible en PHP via la classe Normalizer de l’extension Intl, convertit les variantes de compatibilité, dont les caractères pleine largeur, vers leur forme canonique standard avant toute comparaison :

function contient_mot_interdit(string $texte, array $liste_noire): bool
{
    $forme_normalisee = \Normalizer::normalize($texte, \Normalizer::FORM_KC);

    if ($forme_normalisee === false) {
        $forme_normalisee = $texte;
    }

    $forme_normalisee = mb_strtolower($forme_normalisee, 'UTF-8');

    foreach ($liste_noire as $mot) {
        if (str_contains($forme_normalisee, mb_strtolower($mot, 'UTF-8'))) {
            return true;
        }
    }

    return false;
}

Après passage par Normalizer::normalize() avec la forme FORM_KC, un caractère pleine largeur comme A se réduit à son équivalent ASCII A. La comparaison qui suit porte alors sur une forme canonique, indépendante de la façon dont l’utilisateur a saisi ou copié son texte.

L'essentiel à retenir : Les caractères pleine largeur ont un point de code différent des lettres ASCII ; Une liste noire compare des chaînes brutes, pas des apparences ; La normalisation NFKC ramène ces variantes à leur forme standard

Variantes selon le contexte d’utilisation

  • Pour un champ de pseudonyme public, appliquer la normalisation à la fois à la saisie et à la liste noire elle-même, afin d’éviter qu’un mot de la liste soit lui-même écrit dans une forme non canonique par erreur de copier-coller.
  • Pour un champ de recherche où l’exactitude compte moins que la tolérance, combiner la normalisation avec une suppression des marques diacritiques via iconv('UTF-8', 'ASCII//TRANSLIT', $texte), qui élimine aussi les accents.
  • Pour une modération à fort enjeu, ne jamais se reposer uniquement sur une liste noire de mots, même normalisée : une revue humaine des signalements reste nécessaire, la liste noire ne filtrant qu’un premier niveau de tentatives grossières.

Ce que cette normalisation ne couvre pas

D’autres familles de caractères posent des problèmes similaires sans passer par la normalisation NFKC, notamment les caractères combinants ajoutés entre les lettres d’un mot ou certains caractères de contrôle invisibles insérés au milieu d’une chaîne. Une défense en profondeur combine généralement la normalisation Unicode, une limitation stricte des catégories de caractères autorisés via une expression régulière fondée sur les catégories Unicode, et une revue humaine des cas ambigus.

Vérifier l’effet de la normalisation avant déploiement

Un test unitaire simple confirme que la fonction se comporte comme attendu : soumettre une chaîne composée exclusivement de caractères pleine largeur correspondant à un mot de la liste noire, et vérifier que la détection se déclenche bien après normalisation. Ce test doit être conservé dans la suite de tests du projet, car une future modification de la fonction de comparaison, par exemple pour optimiser sa performance, pourrait réintroduire une comparaison sur la chaîne brute sans normalisation préalable, sans que cela soit immédiatement visible en relecture de code.

Pour aller plus loin

Documenter, dans le code de modération, la liste des transformations appliquées avant comparaison évite qu’une future modification de la liste noire soit testée uniquement avec des caractères ASCII standard, en oubliant les variantes qui ont justifié la normalisation en premier lieu.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Partager :

À propos de l'auteur

WordPress Développement

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi