Ni spam ni captcha
Tagged:  •    •  

Mettre en ligne un formulaire quelqu'il soit (contact, livre d'or...) s'est être à peu près sûr de se faire massivement spammer, même si votre site n'a pas une audience très élevée.
L'un des remèdes utilisés généralement est le captcha (les caractères à lire sur une image et à saisir dans un champ texte). Sauf que déjà de base il va à l'encontre des principes de l'accessibilité du Web mais en plus, il marche tellement bien que parfois, quelqu'un qui veut utiliser votre formulaire ne le peut pas, faute de pouvoir lire correctement les caractères requis.

D'où la problématique : comment se protéger (au maximum) du spam, sans utiliser de captcha. Je vais vous présenter ci-dessous différentes techniques complémentaires. J'indiquerai en plus quelques conseils pour la mise en pratique dans CakePHP 1.2.

Les champs cachés qui doivent rester vides

Le spam est envoyé via des robots qui parcourent le Web. Ces robots ne sont pas soumis aux mêmes contraintes d'affichage qu'un navigateur. Eux, ne voient que le code des pages.
Vous pouvez donc créez un bloc contenant plusieurs champs (plus vous en mettez plus vous avez de chances que le robot tombe dans le panneau).
Via CSS, cachez ensuite ce bloc de champs (display: none). L'utilisateur ne verra pas les champs à l'écran mais le robot les verra parfaitement dans son code.
Enfin, créez une règle pour vérifier que ces champs doivent être vides à la soumission du formulaire. Avec Cake, il suffit dans le modèle d'assigner ces champs à la règle ("rule") "blank".

Dans le modèle :

'first_name' => array(
'required' => true,
'allowEmpty' => true,
'rule' => 'blank',
'message' => 'Ce champ doit être laissé vide.'
),

Dans la vue :

echo $html->div(
'required-fields',
$form->input('first_name').
$form->input('last_name').
$form->input('nick_name').
$form->input('age').
$form->input('country').
$form->input('e-mail').
$form->input('site_url').
$form->input('subject')
);

Dans le CSS :

#livre-dor .required-fields {
display: none;
}

Le chronomètre

Les robots soumettent les formulaires instantanément. Vous pouvez donc, mettre un champ caché ("type=hidden") contenant l'instant auquel a été généré le formulaire.
A la soumission du formulaire, il suffit de vérifier qu'il s'est écoulé plus de x secondes (disons 5 ou 10).
Pour Cake, créer dans le modèle un champ timer, lui associer la règle "validateTimer". Dans cette méthode, vérifier le temps écoulé. Dans le contrôleur, créer et initialiser le champ timer à chaque chargement du formulaire.

Dans le modèle :

'timer' => array (
'required' => true,
'allowEmpty' => false,
'rule' => array('validateTimer', 5),
'message' => 'Vous avez validé le formulaire trop rapidement (5 secondes minimum).'
),

...

function validateTimer($datas, $duration) {
return (time() - current($datas)) > $duration;
}

Dans le contrôleur (à la fin de la méthode associée à l'action affichant le formulaire) :

$this->data['Message']['timer'] = time();

Dans la vue :

echo $html->div(null, $form->input('timer', array('type' => 'hidden')));

Changer les noms des "vrais" champs

Les robots ont une liste pré-établie de données à écrire dans les champs des formulaires, suivant le nom de chaque champ. Vous pouvez donc mettre des noms farfelus pour les champs que vous utilisez réellement. Soit vous répercutez ces noms sur la BD et ça marche tout seul. Soit vous gardez des noms corrects en BD et la solution que je vous conseille alors est de garder les noms bizarres dans la définition des règles de validations ($validate) du modèle et de faire la correspondance entre les données dans "beforeSave".

Détecter des mots caractéristiques du spam

Les messages de spam vous proposent le plus souvent d'acheter des montres ou des pilules bleues ou ce genre de trucs.
Une technique simple consiste à créer une table et un module (M + C + V) pour gérer des mots interdits. Pas besoin d'illustrer le code de cette partie, vous êtes libres.
Il suffit ensuite d'associer le champ qui représente le message saisi par l'utilisateur à une règle du genre "validateCensoredWords".

Dans le modèle :

function validateCensoredWords($check) {
App::import('Model', 'CensoredWord');
$censorModel = new CensoredWord();

$words = $censorModel->findAll();
foreach($words as $word) {
if (preg_match('#'.$word['CensoredWord']['word'].'#i', $check) != 0)
return false;
}

return true;
}

Je ne prétends pas que cette implémentation est la meilleure possible... Si vous avez mieux, n'hésitez pas poster en commentaire de ce billet.

Associer chaque formulaire à une valeur unique

Une caractéristique de certains robots (peut-être tous, je ne suis pas spécialiste de ce point là) est la mise en cache des formulaires pour poster leurs messages plus rapidement. Un moyen simple de contrer cela est d'associer un formulaire à une valeur unique.
Créez une table et un module pour gérer ces valeurs uniques. Y intégrer une méthode générant une valeur unique (cf. exemple plus loin). Dans le contrôleur, créer et initialiser le champ unique_value à chaque chargement du formulaire en se servant de cette méthode. Il suffit ensuite d'associer le champ qui représente le message saisi par l'utilisateur à une règle du genre "validateUniqueValue".

Dans le modèle :

function validateUniqueValue($check) {
App::import('Model', 'UniqueValue');
$uniqModel = new UniqueValue();

$res = $uniqModel->findByValue($check);
return empty($res);
}

Encore une fois, je pense qu'on peut faire mieux comme implémentation (utiliser hasAny par exemple).

Dans le contrôleur (à la fin de la méthode associée à l'action affichant le formulaire) :

$this->data['Message']['unique_value'] = $this->UniqueValue->generate();

Pour générer une valeur, vous pouvez utiliser String::uuid() (méthode CakePHP) ou uniqid() (fonction PHP) :

function generate() {
do {
$value = uniqid(rand(), true);
$res = $this->findByValue($value);
} while(!empty($res));

return $value;
}

Limiter le nombre de liens que l'utilisateur peut entrer

Puisque les spammeurs veulent vous vendre des trucs, il faut qu'ils donnent les liens vers les sites proposant ces produits. Et généralement, ils remplissent leurs messages de liens hypertextes. On peut donc limiter le nombre de liens qu'il est possible de saisir dans le message.
Pour cela, créer une méthode du genre "validateLinksNumber" et associer cette règle au champ représentant le message saisi par l'utilisateur.

Dans le modèle :

function validateLinksNumber($datas, $maxLinksNumber) {
return preg_match_all("#<a[[:space:]]*href#i", current($datas), $matches) <= $maxLinksNumber;
}

Conclusion

Il y a surement d'autres techniques qu'il est possible de mettre en place. Et il n'est pas obligatoire de mettre en œuvre toutes les méthodes présentées ci-dessus, à vous de faire votre choix.
Si vous voulez proposer de nouvelles idées ou proposer des modifs sur celles présentées ci-dessus, n'hésitez pas à poster des commentaires.

Pourquoi pas...

Tu as tout a fait raison: les spams ne sont pas une fatalité et les captcha ne sont pas une obligation. Merci pour toutes ces solutions simples qui ne demandent qu'a etre mises en place. Peut etre pourrait on meme faire un petit composant generique pour cake?

Sinon, en utilises-tu actuellement sur ton site? Si oui lesquelles?

tout à fait

Oui, j'utilise certaines de ces techniques sur mon site.

En fait, quand j'ai mis en ligne le livre d'or réalisé avec CakePHP, je n'avais pas du tout pensé à ce problème du spam. Le lendemain, j'avais 5 messages dont 2 contenant du JavaScript, ce qui avait complètement pourri la page.

J'utilise tout ce que j'ai présenté, sauf le point consistant à mettre des noms bidons pour les "vrai" champs. Méthodes utilisées aussi bien sur la page de contact que sur le livre d'or. Ajouter à cela, évidemment, des traitements pour passer à la moulinette les données saisies par l'utilisateur (suppression des balises script par exemple).

Dia

Très ingénieux

Bonjour,

Tout d'abord petite question qui n'a rien a voir... Tu n'utiliserais pas Drupal à tout hasard? Je l'ai abandonné il y a 6 mois après 2 ans d'utilisation et j'ai cru reconnaitre sur ce site ce cher Drupal :)

Et sinon, pour l'article en lui-même.
Ces méthodes sont très ingénieuses et pas lourdes à mettre en œuvre à mon gout.
La méthode des champs cachés est vraiment très bonne, et il fallait y penser.
Personnellement j'utilise un captcha, mais je me rend compte que cela pose problème a pas mal de personnes et je me dis que la meilleure protection contre tout ce que l'on peut trouver: c'est le fait d'utiliser quelquechose de différent des autres. Les robots cherchent à toucher le plus grands nombres de personnes/sites, et deviennent aptes à détecter et passer les captchas traditionnels, donc autant s'armer d'autre chose, peut être plus simple mais pas traditionnel et donc qui résistera plus longtemps je pense (parce que pas connu).

A bientôt!

merci

bonjour,

nous utilisons en effet Drupal pour ce blog

je rajouterai qu'en plus d'être moins courantes que le captcha, ces méthodes sont plus fiables
depuis un moment déjà, ça m'énerve à chaque fois que je dois renseigner un captcha, j'ai parfois du mal à lire ce qui est marqué !

++

Dia