Formulaire PHP anti-spam (captcha)

Les temps sont durs et le spam est devenu la vraie chienlit du net. Ayant dernièrement souffert des attaquent nombreuses sur mes formulaires, via des robots sans vergogne, j'ai vite décidé de développer une protection. Un captcha. Ce n'est pas nouveau car en place sur de nombreux sites (notamment les inscriptions aux forums) mais c'est juste une façon de voir la chose...

Un seul fichier : le formulaire récursif

Ce script utilise les session PHP, et la gestion classique de formulaires. Il faut lui ajouter un répertoire contenant des images/caractères, petit fichiers images au format GIF, contenant autant de caractères alphanumériques que souhaités. En voici sept. Le script vérifie la présence de ce répertoire et implémente automatiquement la série codée via le tableau $alphabet.

Une session $_SESSION['code'] garde en mémoire le code composé aléatoirement, à partir des noms de fichiers (a.gif, b.gif, 2.gif, etc.) sans extension, et une série $_SESSION['img_1'], $_SESSION['img_2'], etc. qui mémorise autant de noms de fichiers que $nbrchars le demande, afin de composer l'image $imagecode ensuite affichée.

La variable $erreur arrête le traitement du formulaire et permet l'affichage du type d'erreur rencontrée. Sinon, le formulaire redirige le visiteur vers une page $confirmation (index.php ira bien) qui affichera un message de confirmation grâce à au drapeau $_GET['mailok']. A noter qu'en cas de traitement récursif de la confirmation (auquel cas l'affichage du formulaire HTML doit être conditionnel) la session $_SESSION['code'] étant vidée, il est impossible de multiplier son envoi en rechargeant la page : le code toujours présent dans la variable $_POST['vateuf'] est différent de celui de la nouvelle session ! Une pierre, deux coups :-)

Fichier contact.php

<?php
#
# Editer ces variables
#
# répertoire des images sous la forme a.gif, b.gif, 1.gif, etc.
$rep_lettres = "netAlbum2/imgsys/lettres/";
# nombre de caractères (repris dans maxlength)
$nbrchars = 4;
# fichier de redirection, affichant la confirmation
$confirmation = "./";
#
# fin de l'édition
#
session_start();
$imagecode = "";
$erreur = ""; # toutes erreurs formulaire

# le formulaire a été envoyé on vérifie la correspondance
if($_POST && $_POST['vateuf'] != $_SESSION['code']) {
	$erreur = "Les caractères ne correspondent pas !";
	$_POST['vateuf'] = "";
}

# pas de session code ou demande d'un nouveau code
# implémentation des sessions (si le répertoire existe)
if((!$_SESSION['code'] || $_GET['newcode']) && $dossier = @opendir($rep_lettres)) {
	# si un nouveau code a été demandé on efface la session
	if($_GET['newcode']) $_SESSION['code'] = "";
	while ($fichier = @readdir($dossier)) {
		if( $fichier == "." || $fichier == ".." || is_dir($fichier) ) continue;
		# on implémente le tableau $alphabet des fichiers/caractères
		$alphabet[] = $fichier;
	}
	@closedir($dossier);
	$nbrimg = count($alphabet)-1;
	# tirage au sort des $nbrchars caractères composant le code
	for($i=0; $i<$nbrchars; $i++)
		$lettre[] = rand(0,$nbrimg);
	# implémentation de l'image $imagecode affichée,
	# de la session 'code' contenant la série de caractères à comparer
	# et des $nbrchars sessions préfixées par 'img_'
	$i=0;
	foreach($lettre as $val) {
		$imagecode .= "<img src='$rep_lettres$alphabet[$val]' alt=''>";
		$_SESSION['code'] .= basename($alphabet[$val],'.gif');
		$_SESSION['img_'.$i] = $alphabet[$val];
		$i++;
	}
# composition de l'image $imagecode si la session est implémentée
} elseif($_SESSION['code']) {
	for($i=0; $i<$nbrchars; $i++)
		$imagecode .= "<img src='".$rep_lettres.$_SESSION['img_'.$i]."' alt=''>";
}

if($_POST && !strlen($erreur)) {

	# pas d'erreur de code : traitement des éléments de formulaire...
	
	# ... une fois tout vérifié et mail envoyé on efface la session
	$_SESSION['code'] = "";
	# redirection vers $confirmation où l'on affiche un message
	# grâce à la variable $_GET['mailok']
	@header('Location: '.$confirmation.'?mailok=1');
	exit;
	
}
?>
<!-- formulaire simplifié -->
<!-- script développé par Pierre Pesty http://dev.ppan.net -->
<form action="<?= $_SERVER['PHP_SELF']?>" method="post">
<? if(strlen($erreur)) { ?>
<? echo "$erreur\n"?>
<? } ?>
<input name="Nom" value="<?= htmlentities($_POST['Nom'],ENT_QUOTES)?>">
<? if($imagecode) { ?>
<? echo "$imagecode\n"?><input type="text" name="vateuf" size="<?= $nbrchars?>" maxlength="<?= $nbrchars?>" value="<?= htmlentities($_POST['vateuf'],ENT_QUOTES)?>">
 Recopiez les caractères à gauche SVP <a href="<?= $_SERVER['PHP_SELF']."?newcode=1"?>">Nouveau</a>
<? } ?>
<input type="submit" value="Envoyer">
</form>

Variante

Pour les paranos, on peut remplacer les caractères uniques (imaginant que les robots vont lire le code dans les balises <img>) par des fichiers images contenant une série de codes. Auquel cas on devra implémenter un tableau à double entrée : nom de l'image => code correspondant. Le nom de l'image n'ayant alors plus aucune importance. Ou encore écrire l'image via la librairie GD, sur le modèle écrit par l'ami Erwan :

[Haut de page]

<?
// par Erwan http://www.kafarnaum.net/
if ($string == "")
$string = "nothing";
$l = strlen($string);
$WIDTH = $l * $xchar * 2;
$HEIGHT = $ychar * 3;

$img = imagecreatetruecolor($WIDTH, $HEIGHT);
imageantialias($img, true); // selon config PHP
$WIDTH--;
$HEIGHT--;
$clear = imagecolorallocatealpha($img, 255, 255, 255, 0);
$black = imagecolorallocatealpha($img, 0, 0, 0, 0);
$grey = imagecolorallocatealpha($img, 127, 127, 127, 0);

imagefilledrectangle($img, 0, 0, $WIDTH, $HEIGHT, $clear);
for ($i = 0; $i < $l; $i++) {
  $x1 = rand($xchar * 2 * $i, $xchar * 2 * ($i + 1));
  $x2 = rand($xchar * 2 * $i, $xchar * 2 * ($i + 1));
  imageline($img, $x1, 0, $x2, $HEIGHT, $grey);
}
imageline($img, 0, rand(0, $HEIGHT), $WIDTH, rand(0, $HEIGHT), $grey);
for ($i = 0; $i < $l; $i++) {
  $x = rand($xchar * 2 * $i, $xchar * 2 * ($i + 1) - $xchar);
  $y = rand(0, $HEIGHT - $ychar * 1.5);
  imagestring($img, $font, $x, $y, $string[$i], $black);
}

header("Content-type: image/png");
imagepng($img);
imagedestroy($img);
?>

La Société Générale a poussé le vice du côté javascript : écrire le code à l'aide de la souris, à travers un pop-up ! Pour tester, entrez un code quelconque et cliquez.

Contrôle des en-têtes

Sur le modèle décrit en détail sur cette page www.phpsecure.info on peut se passer du subterfuge code/image, en contrôlant les en-têtes ainsi que le corps du message. Partant du principe qu'aucun saut de ligne (\n ou \r) n'est accepté dans $headers ou $destinataire que des entrées de type bcc: ou cc: ou from: n'ont rien à faire dans le corps du message $message_final, on peut insérer :

[Haut de page]

<?
$erreur = "";
$headers = "From: ".$POST['Email'];
// avant l'envoi du mail test des champs sensibles
if(eregi("(\r|\n)",$headers) || eregi("(\r|\n)",$destinataire) || eregi("(cc:|bcc:|from:)",$message_final))
	$erreur = "Tu t'es vu quand tu spam ?..."; // bloquage du mail
// ajout des en-têtes optionnelles après vérification
if(strlen($email_cc))
	$headers .= "\nCC: ".$email_cc;
if(strlen($email_bcc))
	$headers .= "\nBCC: ".$email_bcc;
// pas d'erreur...
if($_POST && !strlen($erreur)) {
	// ... traitement du formulaire
} elseif(strlen($erreur)) {
	echo $erreur;
}
?>
Neuneu a commenté le 22.11.2006

Ou peut-on télécharger ce script pour les neuneux ?

Pierre a commenté le 22.11.2006

Neuneu : il suffit de copier-coller dans un editeur de texte et de sauvegarder le fichier.
Par exemple avec le doux nom contact.php ;-)

Christian a commenté le 08.05.2006

Bonjour,
De façon à ne plus me retrouver avec des dizaines d'entrées parasites par jour dans un Livre d'Or, j'ai déjà mis en place le système que vous préconisez, basé sur des images élaborées avec la bibliothèque GD de PHP. Les caractères (lettres et chiffres, majuscules ou miniscules) sont générés à chaque fois de façon totalement aléatoire, en utilisant une police qui les fait apparaître en grande taille et assez épais, en rouge sur fond jaune (ce qui peut évidemment être modifié au besoin). Techniquement parlant l'application marche super bien, mais je peux vous assurer que ça n'a rien changé et que les robots continuent à me polluer mon Liver d'Or à qui mieux mieux. On peut bien évidemment supposer que les caractères sont encore trop discernables dans l'image et compliquer encore et c'est ce que je me propose de faire (avec des ombres portées derrière les caractères par exemple de façon à ce que l'analyse de l'image par les robots soit rendue plus difficile) et ceci jusqu'à la limite de lisibilité pour un oeil humain, mais je subodore qu'en fait les robots parviennent à intercepter le code en clair qui est stocké (dans un cookie ?) pour comparaison ultérieure pour vérification avec le code entré dans le formulaire...
Votre avis serait apprécié !
C. Chamoley

Pierre a commenté le 08.05.2006

Christian : a priori, il y a anguille sous roche en dehors du filtrage par image. Le code n'est pas écrit dans un cookie mais dans une session PHP, inaccessible par le client (puisque côté serveur).
Essayez le contrôle des en-têtes, décrit en bas de cette page (ci-dessus).

[Réagir à cet article]

http://dev.ppan.net [Haut de page] [Document mis à jour le 05.04.2007]