Système anti-spam pour formulaires (captcha)

Dernière m.à.j. : 2018-06-17

1. De quoi s'agit-il ?

Un formulaire permet à un visiteur d'interagir avec un site. Changement de design, recherche à travers le site ou écriture un message, telles sont ses utilisations les plus fréquentes. Mais les visiteurs sur un site ne sont pas tous humains ...

Parce que des robots d'indexation de moteurs de recherches ou des robots à la recherche d'informations scannent le web, il faut pouvoir protéger l'envoi d'un formulaire.

Protéger l'envoi ne veut pas dire pour autant le rendre inaccessible, mais simplement s'assurer que la soumission d'un formulaire n'est pas une action faite par un de ces "robots". Le cas le plus fréquent est le formulaire de contact : mal sécurisé, on peut envoyer un formulaire vide (et plus si affinités !)

Beaucoup de sites proposent ce qu'on appelle un "captcha", c'est à dire un code d'image à recopier pour valider le formulaire. Les images sont volontairement déformées afin d'éviter que les fameux "robots" ne reconnaissent les lettres et valident le formulaire. Seulement ce système, de plus en plus poussé, devient de plus en plus ... inaccessible ! En effet, qui n'a jamais hésité entre un L, un 1 et un i ? Entre un O et un 0 ? Les techniques de brouillage de ces images n'en facilitent pas les choses.

2. La solution ?

Soyons clairs : il n'y a pas, et il n'y aura probablement pas, de solution absolue. Toute parade protège pour un temps, jusqu'à ce que "le truc soit découvert".

J'ai ainsi imaginé une solution d'image pour valider un formulaire, mais qui fait appel à une faculté d'abstraction qu'a l'être humain de pouvoir associer deux idées et que ne peuvent pas avoir les "robots" : au lieu d'une image, on fait calculer une opération mathématique pour mon exemple, mais peut être aussi autre chose, et l'humain doit en donner le résultat. Le résultat n'est donc pas une pâle copie de l'image ... (Notez qu'on peut se passer d'image, d'ailleurs, la question et la réponse étant avant tout du texte... L'image n'est là que pour ressembler aux fameux "captchas" sur le web).

Ce type de solution présente également l'avantage de préserver l'accessibilité d'un site.

3. Mise en place

La mise en place se fait à deux niveaux : coté formulaire, et coté traitement de formulaire.

3.1. Les prérequis

  • une page PHP pour le formulaire (qui peut être un code PHP qui génère le formulaire)
  • une page PHP pour le traitement du formulaire (cela peut être la même page que précédemment)
  • une fonction PHP qui contiendra, comme une base de données, les images éventuelles et les questions réponses
  • des images qui seront les futures "énigmes"
  • un peu de temps devant soi ...

3.2. Le principe

Le principe est relativement simple. Lorsque le formulaire HTML est affiché, une image/question de la fameuse fonction est tirée au sort et affichée dans le formulaire. Le lien vers sa réponse est envoyé via le formulaire en caché.

Lorsque le formulaire est validé, on récupère tous les champs, dont la réponse du visiteur à l'image/question ainsi que le fameux lien vers la "vraie" réponse.

Grâce à ce lien, on sort la vraie réponse et on compare par rapport à celle du visiteur ...

3.3. Partie 1 : la fonction

Cette fonction est un code php que l'on peut placer dans un fichier externe, par exemple antispam.php. On y renseigne :

<?php
	/*
	 * NoSpamQuestion affiche une question pour la validation d'un formulaire ...
	 * $mode, mode question ou réponse par défaut tirage au sort de question {string}
	 * $answer, lors de la demande d'une réponse à la question numero tant ... {int}
	 *
	 * @returns array
	 *
	 * Ajouter une question :
	 * copier/coller ces lignes et remplir le contenu entre guillemets doubles :
	 *
	 * $array_pictures[$j]['num'] = $j; // ne pas changer cette ligne
	 * $array_pictures[$j]['question'] = "mettre ici la question (correspondant à l'image si vous utilisez une image)";
	 * $array_pictures[$j]['answer'] = "mettre ici la réponse à l'énigme";
	 * $j++; // ne pas oublier cette ligne dans la copie :-)
	 *
	 * C'est tout. Question suivante ? :-)
	 *
	 */
	function NoSpamQuestion($mode, $answer)
	{
		$array_pictures = array(); $j = 0;

		$array_pictures[$j]['num'] = $j;
		$array_pictures[$j]['question'] = "La multiplication de 4 par 4 donne ...";
		$array_pictures[$j]['answer'] = "seize";
		$j++;
		$array_pictures[$j]['num'] = $j;
		$array_pictures[$j]['question'] = "La multiplication de 2 par 2 donne ...";
		$array_pictures[$j]['answer'] = "quatre";
		$j++;

		if ($mode != 'ans') // mode : 'ask'
		{
			// on est en mode 'tirer au sort', on tire une image aléatoire
			$lambda = rand(0, count($array_pictures)-1);
			return $array_pictures[$lambda];
		}
		else
		{
			// on demande une vraie réponse
			foreach($array_pictures as $i => $array)
			{
				if ($i == $answer)
				{
					return $array;
					break;
				};
			};
		}; // Fin if ($mode != 'ans')
	}
?>

Voilà pour la "base de données". Pour rajouter une question, il suffit de copier coller à la suite des autres les lignes comme expliqué dans le commentaire précédant la fonction.

On peut y adjoindre une image, il suffit de rajouter quelques lignes aux fichiers suivants. Par simplicité, j'ai seulement laissé le texte.

Attention, la réponse finale ne doit pas être la question. Voyez les exemples que j'ai laissés... Le plus simple à faire est une opération mathématique, mais vous pouvez demander également "comment s'appelle l'invention qui permet de rentrer dans une maison ?" avec pour réponse "porte".

Faites attention également à ceci : des gens peuvent répondre "porte", ou "la porte" ou encore "une porte" : soyez précis dans les questions pour n'avoir qu'une réponse unique et sans ambigüité. (exemple : mettez alors dans la question un indice, comme "en 5 lettres"). Et une image correspondante montrant une maison avec une belle porte pointée par une grosse flèche rouge...

3.4. Partie 2 : le formulaire HTML

Il s'agit maintenant d'incorporer ce système dans un formulaire. Le formulaire, écrit dans un langage HTML, doit être dans une page d'extension .php (sinon nous ne pourrons pas faire appel au PHP !)

Dans ce formulaire :

  • On appelle le fichier "antispam.php" afin d'avoir accès au contenu des questions;
  • on tire au sort une question;
  • on l'insère dans le formulaire avec les champs appropriés.

Voici, concrètement, comment faire dans mon fichier formulaire.php :

<?php
	// on inclue le fichier des questions/réponses
	require_once('antispam.php');

	// on tire au sort une question
	$nospam = NoSpamQuestion('ask', 0);
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Formulaire basique</title>
</head>

<body>
  <!-- ici se situe le code HTML de la page -->
</body>
</html>

A ce stade, la variable PHP nommée $nospam (ligne 6) contient un jeu de 3 lignes tirées au sort du tableau précédent.

Dans le formulaire HTML, on rajoute 2 champs : l'un pour que le visiteur réponde à la question anti spam, l'autre pour le lien vers la "vraie" réponse (champ caché) :

<p>
	<label for="code">Ecrivez en LETTRES le résultat : <?php echo $nospam['question']; ?></label><input type="text" name="code" id="code" />
	<input type="hidden" name="nospam_question" value="<?php echo $nospam['num']; ?>" />
</p>

3.5. Partie 3 : le traitement PHP

Le traitement PHP consiste à récupérer les contenus des champs du formulaire précédemment soumis [cf. Traitement de Formulaires en PHP]. On récupère donc de manière habituelle tous les champs, plus deux autres : le code, et le numéro de question; l'un étant ce que le visiteur pense être la réponse à l'énigme, et l'autre le lien vers la vraie réponse.

Si la page de traitement du formulaire est différente de la page de l'affichage, il faut appeler à nouveau le fichier antispam pour redéfinir les questions.

Enfin, on envoie à la fonction le lien vers la réponse et celle-ci nous retourne la réponse. Il ne reste plus qu'à comparer la réponse du visiteur et la "vraie" réponse de la fonction, et le tour est joué !

Concrètement dans traitement_formulaire.php (si tel est le nom du fichier dans l'action de votre formulaire) :

<?php
	/* ATTENTION : si le formulaire a une méthode method="get", remplacez $_POST par $_GET */

	// on ne traite le formulaire que si le bouton submit a été cliqué
	if (isset($_POST['submit']))
	{
		require_once('antispam.php'); // pour définir les images, les questions et les réponses

		// récuperation des variables
		/*
			Récupérez ici vos variables du formulaire
		*/

		// n'oublions pas les 2 variables du captcha :
		$code = (isset($_POST['code'])) ? strtolower($_POST['code']) : ''; // contient la réponse du visiteur
		$nospam_question = (isset($_POST['nospam_question'])) ? $_POST['nospam_question'] : ''; // contient un nombre : le numéro de la vraie réponse

		// On demande la vraie réponse
		$verif_nospam = NoSpamQuestion('ans', $nospam_question);

		// on compare la 'vraie' réponse et celle du visiteur
		if ($code != strtolower($verif_nospam['answer']))
		{
			// le formulaire s'arrête ici
			echo '<p>Vous n\'avez pas répondu correctement à la question ....</p>';
		}
		else
		{
			// traitement du formulaire comme souhaité ...

			[...]

			echo '<p>Merci pour votre participation ...</p>';
		};
	};
?>