NoSPAM Charabia

Un plugin complémentaire de NoSPAM, qui bloque les soumissions de formulaire remplis de texte aléatoire, du genre azVIkQyEgyGNYAcEyLcDI ou gfaabbVsLbqeodWX. C’est ce que produit un robot qui remplit un formulaire sans le comprendre : il obtient un jeton valide en chargeant la page, évite le champ piège masqué en CSS, et poste du charabia dans tous les champs texte.

Aucune friction pour l’internaute : pas de captcha, pas de case à cocher, pas de service tiers, rien à charger dans la page.

Principe

Une valeur est retenue comme charabia si elle est un seul mot d’au moins 8 lettres non accentuées, sans espace, et qu’elle porte l’un des trois motifs qu’aucune langue à alphabet latin ne produit :

  • au moins trois sauts de casse à l’intérieur du mot : gfaabbVsLbqeodWX, azVIkQyEgyGNYAcEyLcDI. Une raison sociale composée en porte un ou deux — EcoMobile, InstitutAgroSud, FromagerieDuCausse — là où une chaîne tirée au hasard en produit cinq en médiane ;
  • ou huit consonnes consécutives dans un mot pauvre en voyelles (moins de 42 %) : fghjfghkj, jnklnkjnjknjkn, le doigt qui traîne sur le clavier ;
  • ou une même lettre répétée quatre fois : dddddddddd, xxxxxxxx.

Le taux de voyelles ne s’applique qu’au motif des consonnes : azVIkQyEgyGNYAcEyLcDI en compte 48 %, y inclus, tout en portant sept sauts de casse.

La soumission est refusée à partir de deux champs marqués.

Calibration

Les seuils ne sont pas choisis au jugé. Ils sont calés sur deux corpus réels de réponses de formulaire SPIP, 530 000 en tout, en cherchant à chaque fois le réglage qui ne refuse aucune contribution humaine :

réglage ce qu’il bloque en plus ce qu’il prend à tort
2 sauts de casse 16 réponses FromagerieDuCausse, saisi dans deux champs par une entreprise de ce nom : 2 soumissions réelles refusées
3 sauts (défaut) rien
5 consonnes consécutives 25 réponses un patronyme germanique saisi dans deux champs : 2 soumissions réelles refusées. Et toute une famille de noms à un champ
7 consonnes 2 réponses Hirschsprung, Schtschedrin : les patronymes les plus denses atteignent exactement sept
8 consonnes (défaut) rien

C’est la leçon principale de la calibration : le faux positif d’un tel filtre, c’est un patronyme. Refuser un formulaire à quelqu’un à cause de son nom de famille n’est pas acceptable, et cela se produit à des seuils qui paraissent pourtant raisonnables.

Résultat avec les réglages par défaut :

corpus réponses bloquées faux positifs
site A 4 063 64, toutes du spam 0
site B 525 577 1 083 (0,21 %) 0

Sur le site A, les 64 réponses bloquées sont l’intégralité du spam reçu, sur quinze formulaires et quatre vagues étalées sur dix mois : aucun faux négatif non plus. Sur le site B, chaque valeur marquée apparaissant plusieurs fois a été inspectée à la main : robots, scanners de vulnérabilité, et saisies de test laissées par des humains qui essayaient le formulaire.

Ce que ce plugin ne fait pas

Il ne détecte que ce motif-là. Un spam rédigé en langue naturelle — mots de dictionnaire concaténés, texte produit par un modèle de langue — passe sans être vu, et c’est par construction.

C’est pourquoi chaque soumission évaluée crée une ligne de log, y compris celles qui passent : c’est la seule façon de voir un bot changer de méthode.

Deux catégories de soumissions ne sont jamais évaluées, à connaître avant de croire un formulaire couvert :

  • les formulaires à étapes de Saisies. Chaque étape ne poste qu’une partie des champs, et la vérification n’en voit jamais l’ensemble : le plugin passe son tour plutôt que de juger sur un fragment. Un tel formulaire n’est donc pas protégé du tout ;
  • les soumissions d’un administrateur connecté (0minirezo), quels que soient les champs.

Le plugin complète nospam, il ne le remplace pas : jeton, champ piège, listes d’IP et modération des forums restent son affaire. Quand nospam est installé, un refus lui signale en retour l’IP du visiteur, que nospam met alors de côté 24 h pour ses propres formulaires : le refus déborde donc le formulaire où il s’est produit.

Installation

Le plugin ne nécessite aucune configuration une fois activé.

Si nospam est installé, le plugin reprend sa liste de formulaires : tout ce qui lui est déjà confié est couvert, y compris les formulaires du plugin Formidable, qui s’y inscrivent d’eux-mêmes. Sinon, la liste par défaut est formidable, inscription, forum, ecrire_auteur, signature, recommander.

Si saisies est installé, la déclaration des champs du formulaire fait foi pour décider quoi analyser. C’est elle, et elle seule, qui dit qu’un champ est un mot de passe ou une adresse email. À défaut, le plugin retombe sur un filtrage par nom de champ.

Surveiller un formulaire de plus

Quand nospam est installé, passer par son pipeline habituel nospam_lister_formulaires : le formulaire hérite du même coup de son jeton et de son champ piège à robots.

function monplugin_nospam_lister_formulaires($formulaires) {
	$formulaires[] = 'mon_formulaire';
	return $formulaires;
}

Sinon, sans nospam, utiliser le pipeline nospam_charabia_lister_formulaires :

function monplugin_nospam_charabia_lister_formulaires($formulaires) {
	$formulaires[] = 'mon_formulaire';
	return $formulaires;
}

Écarter un champ de plus de l’analyse

Certains champs ont le droit de contenir ce qui ressemble à du charabia : un mot de passe, un identifiant, une référence. Le détecteur ne les regarde pas : le plugin connaît déjà une trentaine de fragments de nom qui les signalent, listés dans nospam_charabia_fragments_exclus(), et la déclaration Saisies fait foi quand elle est là.

Pour ajouter un champ propre à un site, on peut utiliser le pipeline du même nom :

function monplugin_nospam_charabia_fragments_exclus($fragments) {
	$fragments[] = 'matricule';
	return $fragments;
}

Un fragment n’est reconnu que s’il occupe un mot entier du nom du champ : au début du nom ou après un souligné, à la fin ou avant un souligné ou un chiffre. En déclarant matricule, les champs matricule, matricule_2 et mon_matricule passent donc à travers le détecteur ; matriculeur, lui, reste analysé normalement.

Un changement de casse sépare les mots au même titre qu’un souligné, pour les formulaires qui nomment leurs champs en casse mixte : userPassword, monEmail et urlHTTPSite passent eux aussi à travers le détecteur.

Le but de cette délimitation : les fragments sont courts et se cachent dans des mots courants. Sans elle, lien retirerait client de l’analyse, tel retirerait hotel, site retirerait necessite, pass retirerait passerelle, etc. Un robot pourrait alors remplir ces champs de ce qu’il veut sans que rien ne soit analysé.

Configurations personnalisées

À poser dans config/mes_options.php. Aucune n’est nécessaire au fonctionnement.

constante défaut rôle
_NOSPAM_CHARABIA_MODE 'bloque' 'log' journalise sans jamais refuser
_NOSPAM_CHARABIA_SEUIL 2 champs marqués à partir desquels on refuse
_NOSPAM_CHARABIA_LONGUEUR_MIN 8 longueur minimale d’une valeur analysée
_NOSPAM_CHARABIA_TRANSITIONS 3 sauts de casse exigés ; 0 désactive
_NOSPAM_CHARABIA_CONSONNES 8 consonnes consécutives ; 0 désactive
_NOSPAM_CHARABIA_REPETITIONS 4 répétitions d’une même lettre ; sous 3, désactive
_NOSPAM_CHARABIA_VOYELLES 0.42 taux de voyelles maximal, sur le seul motif des consonnes

Sur un site dont on ne connaît pas encore les contributions, commencer avec _NOSPAM_CHARABIA_MODE sur 'log' quelques semaines et relire le journal avant de bloquer. Un site qui reçoit des références, des codes ou des identifiants dans des champs texte libre mérite ce temps d’observation.

Ne pas descendre _NOSPAM_CHARABIA_SEUIL à 1, ni _NOSPAM_CHARABIA_CONSONNES en dessous de 8, ni _NOSPAM_CHARABIA_TRANSITIONS en dessous de 3 : voir le tableau de calibration.

Avec _NOSPAM_CHARABIA_REPETITIONS en dessous de 3, le motif ne distingue plus rien, il prend Association et Marseille à 2, et absolument tout mot à 1. Une valeur inférieure à 3 est donc ramenée d’office à 0, plutôt que d’appliquer un seuil que personne n’a demandé. Les trois autres motifs continuent d’opérer, et xxxxxxxx reste attrapé par celui des consonnes.

Le message d’erreur est volontairement générique, et identique à celui de nospam, pour qu’un robot ne puisse pas déduire quel filtre l’a arrêté. Pour y ajouter une adresse de contact, surcharger la chaîne dans le lang/ du site :

// lang/nospam_charabia_fr.php
return ['erreur' => 'Impossible de prendre en compte votre message. Écrivez-nous à …'];

Journal

tmp/log/nospam_charabia.log, une ligne JSON par soumission évaluée :

{
  "form": "formidable",
  "n": 5,
  "total": 10,
  "seuil": 2,
  "mode": "bloque",
  "refuse": true,
  "champs": [
    "input_1",
    "input_2",
    "input_3",
    "input_4",
    "textarea_1"
  ],
  "ip": "…",
  "ua": "…",
  "valeurs": {
    "input_1": "gfaabbVsLbqeodWX",
    "input_2": "CqjIOclCOCdEWRazK",
    "input_3": "XkTScYIYOcCsrIAGFsinSb",
    "input_4": "ztqIpOyEgqxOgagQECIM",
    "textarea_1": "vatnFctyxweuBidLjdz"
  }
}

Seules les valeurs des champs marqués y figurent, une entrée par nom listé dans champs, tronquée à 60 caractères : c’est du charabia par construction. Celles des autres champs sont des données personnelles d’internautes, elles ne sont jamais écrites. Un champ à valeurs multiples étant marqué dès qu’une seule de ses valeurs l’est, seule celle-là est écrite, ses voisines restent des saisies d’internaute.

ip et ua suivent la même règle : ils n’apparaissent que sur les lignes portant au moins un champ marqué. Une soumission propre laisse bien une ligne, avec "n": 0, mais rien qui désigne son auteur. Un site public journalise ainsi les robots, pas ses visiteurs.

Une soumission déjà refusée par nospam est tout de même analysée et journalisée, avec "deja_refuse": true. Son refuse reste false : le message d’erreur affiché est celui du premier rempart, celui-ci n’y touche pas. C’est ce qui permet de voir un robot arrêté ailleurs commencer à changer de méthode.

Discussion

No discussion

Add a comment

Avant de faire part d’un problème sur un plugin X, merci de lire ce qui suit :

  • Désactiver tous les plugins que vous ne voulez pas tester afin de vous assurer que le bug vient bien du plugin X. Cela vous évitera d’écrire sur le forum d’une contribution qui n’est finalement pas en cause.
  • Cherchez et notez les numéros de version de tout ce qui est en place au moment du test :
    • version de SPIP, en bas de la partie privée
    • version du plugin testé et des éventuels plugins nécessités
    • version de PHP (exec=info en partie privée)
    • version de MySQL / SQLite
  • Si votre problème concerne la partie publique de votre site, donnez une URL où le bug est visible, pour que les gens puissent voir par eux-mêmes.
  • En cas de page blanche, merci d’activer l’affichage des erreurs, et d’indiquer ensuite l’erreur qui apparaît.

Merci d’avance pour les personnes qui vous aideront !

Par ailleurs, n’oubliez pas que les contributeurs et contributrices ont une vie en dehors de SPIP.

Who are you?
[Log in]

To show your avatar with your message, register it first on gravatar.com (free et painless) and don’t forget to indicate your Email addresse here.

Enter your comment here

This form accepts SPIP shortcuts {{bold}} {italic} -*list [text->url] <quote> <code> and HTML code <q> <del> <ins>. To create paragraphs, just leave empty lines.

Add a document

Follow the comments: RSS 2.0 | Atom