Méthodes de protection contre le SPAM n° 1 : le honeypot
Un sujet pénible depuis les débuts d'Internet est le SPAM (non, pas la viande en conserve, qui existe depuis plus longtemps qu'Internet). Un week-end d'ennui, j'ai réfléchi à la façon dont nous pourrions éviter le SPAM (ou aussi : les robots d'indexation), et de là est née la série « SPAM protection methods ». La partie 1 de cette série traite du blocage des bots de SPAM agaçants sur notre site web, mais peut aussi servir à tenir à l'écart des outils automatisés comme SiteSucker, ainsi que des scripts automatisés via Chromium ou similaire. Comme toujours, et ici tout particulièrement : l'article ne sert que d'inspiration et ne doit pas être copié tel quel sans réflexion. En cas de mauvaise utilisation, le script peut conduire à exclure du site des utilisateurs réguliers (par exemple s'ils utilisent un préchargeur de contenu ou si le navigateur lui-même précharge le contenu).
1. Concept
En principe, nous voulons dans un premier temps détecter le bot et dans un second le bloquer. Mon idée de départ était de bloquer le bot via le fichier htaccess. Dès que notre script détecte un bot, il devait écrire à la fin du fichier htaccess la ligne deny {IP} et ainsi exclure le bot, c'est-à-dire son adresse IP, de notre site. Bonne idée, mise en œuvre problématique : quand j'ai compris que cela supposait de rendre le fichier .htaccess inscriptible avec chmod, mon pouls est curieusement monté à environ 200. Il fallait une alternative, et elle s'appelle « sessions ». Nous enregistrons simplement l'état de l'utilisateur dans la session et les adresses IP bloquées dans une base de données. Chaque fois qu'un nouvel utilisateur arrive sur le site, nous vérifions dans la base si son adresse IP est bloquée. Selon le cas, il reçoit une variable de session que nous pouvons vérifier à chaque chargement de page, ce qui nous épargne quelques requêtes SQL. Attention : les sessions seules ne suffisent évidemment pas, sinon une simple suppression des cookies aide. Le plus problématique est cependant de reconnaître s'il s'agit d'un bot ou non. Ma solution a jusqu'ici simplement consisté à insérer un « lien caché » de la forme Ultra Fancy Link. Un bot de spam, qui ne peut pas reconnaître si le lien est réellement visible (puisque seul l'attribut href est pertinent pour lui, pas la balise style), appelle target.php, où se trouve notre honeypot. Cela a toutefois conduit à ce que des navigateurs ou plugins qui préchargent en partie le contenu appellent eux aussi la page, si bien qu'un utilisateur « normal » peut aussi tomber dans notre honeypot. Et c'est évidemment ce qu'il faut éviter. J'ai pu contourner le problème assez facilement en écrivant le chemin https://example.com/target.php dans le fichier robots.txt. Visiblement, les navigateurs qui préchargent le contenu respectent le robots.txt (du moins les bons), et l'on évite ainsi un « faux positif ». target.php ne fait plus alors que d'alimenter la base de données avec l'adresse IP du navigateur.
2. Mise en œuvre
Étape 1 : base de données
Nous créons d'abord les tables dans la base de données :
CREATE TABLE `spamblock` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`ip` text,
`stamp` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
La table Spamblock contient maintenant l'ID primaire obligatoire, un champ « ip » dans lequel sera ensuite inscrite l'adresse IP, ainsi que « stamp », qui contiendra plus tard l'horodatage.
Étape 2 : target.php
Vient ensuite target.php (vous pouvez bien sûr l'appeler autrement). Elle insère une fois l'adresse IP et l'horodatage dans notre table et définit la variable de session.
<?php
$dbname = "dbname";
$username = "username";
$password = "password";
$pdo = new PDO("mysql:host=localhost;dbname=$dbname", $username, $password);
$ip = $_SERVER['REMOTE_ADDR'];
$stamp = time();
$stmt = $pdo->prepare("INSERT INTO spamblocked (ip, stamp) VALUES (:ip, :stamp)");
$stmt->bindParam(':ip', $ip);
$stmt->bindParam(':stamp', $stamp);
$stmt->execute();
?>
Il faut évidemment remplacer ici dbname, username et password par vos données.
Étape 3 : vérification de la variable
À chaque chargement de page, il faut maintenant vérifier si $_SESSION['spamblock'] vaut true ou false. Soit nous ajoutons automatiquement en tête le script suivant via htaccess : php_value auto_prepend_file "/var/www/virtual/david/check.php" (attention : chemin absolu !), soit nous ajoutons la ligne suivante au début de chaque page :
<?php
session_start();
if (isset($_SESSION['spamblock']) && $_SESSION['spamblock'] === true) die();
?>
Nous vérifions ainsi si la variable $_SESSION['spamblock'] est définie et contient la valeur true. Si oui, nous terminons le script.
Étape 4 : robots.txt
Ensuite, nous ajoutons target.php à notre robots.txt pour ne pas effrayer les « bons » robots d'indexation et ne pas avoir de problèmes de préchargement.
User-agent: *
Disallow: /target.php
La structure du robots.txt devrait être explicite.
Étape 5 : lien caché ou autre redirection
Pour finir, nous insérons le lien caché dans notre page normale, le plus « en avant » possible (index.php, c'est bien ; /unterseite/unterseite/unterseite/irgendeinfile.php, c'est mauvais). Car si le robot a traité toute la page et n'est exclu qu'à la fin, après avoir fait son travail, tout cela n'a plus de sens. On peut aussi faire une redirection en JavaScript. La première possibilité serait l'attribut « display:none » mentionné pour un lien.
<a href='target.php' style='display:none'>
La deuxième possibilité serait JavaScript, ce qui suppose toutefois que le robot ait désactivé JavaScript (et attention : dès qu'un utilisateur normal n'a pas activé JavaScript, il est également exclu. Ceci est donc plutôt une piste de réflexion qu'une solution sérieuse).
<noscript>
<iframe src="target.php"></iframe>
</noscript>
Cela peut certainement être étendu. Si vous avez des idées, n'hésitez pas à les laisser dans les commentaires.
Étape 6 : conclure
Enfin, nous téléversons tous les fichiers, préparons la base de données et, dans le meilleur des cas, avons déjà remplacé dans insert.php les valeurs « Username » et « Password » par les identifiants de notre base de données ;-)
Complément : utiliser le bloqueur de publicités
Qui veut en rajouter une couche peut utiliser le bloqueur de publicités que presque tout utilisateur a installé de nos jours, mais probablement aucun robot. Nous renommons simplement le fichier target.php en subtarget.php et écrivons dans target.php l'iFrame suivant :
<iframe src="subtarget.php" id="advert"></iframe>
Pour un utilisateur « normal », le bloqueur de publicités devrait maintenant bloquer la ressource, alors que, pour un bot, l'iFrame est chargé et l'adresse IP est inscrite dans la base. Au lieu de définir l'id « advert », nous pouvons aussi nommer simplement subtarget.php advertisting.php : cela devrait déjà activer Adblock.