Avertissement Internet Explorer
Attention !!!
Internet Explorer n'est pas compatible avec ce site internet.

Vous êtes actuellement sous Internet Explorer, ce navigateur est obsolète ce qui peut vous poser de nombreux problèmes.

Pour éviter ces problèmes veuillez optez pour un navigateur plus récent.

Plus d'informations en cliquant ici

Chargement
Yann-Elias Bellagnech
Développeur Web
Protéger ses formulaires des Spams en créant un HoneyPot avec Symfony et Fail2ban

Protéger ses formulaires des Spams en créant un HoneyPot avec Symfony et Fail2ban

Temps de lecture : 8 minutes
Voir plus

 Pré-requis

Pour bien suivre ce tutoriel, vous devez être à l’aise avec Symfony 6 et connaître un minimum les commandes de base de Linux et de ses distributions dérivées.

Qu’est-ce qu’un HoneyPot ?

Avant de commencer à coder, il convient de définir ce qu’est un Honeypot. Pour ce faire, je m’appuierai sur la définition donnée par Wikipedia pour qui « un honeypot est une méthode de défense active qui consiste à attirer, sur des ressources (serveur, programme, service), des adversaires déclarés ou potentiels afin de les identifier et éventuellement de les neutraliser. »

Dans le cas qui nous intéresse, celui d’un formulaire sur un site internet, nous allons appliquer cette méthode de la manière suivante.

Nous allons créer un champ de formulaire dont le code HTML sera similaire à un champ de formulaire classique, mais qui sera rendu invisible par le code CSS.

Ainsi,  le champ sera invisible pour un utilisateur lambda, mais pas pour un robot qui ne lirait que le code HTML. Par conséquent, si la valeur du champ venait à ne pas être nulle, on pourrait conclure que le formulaire a été rempli par un robot et on pourrait alors utiliser l’adresse IP de ce robot pour l’empêcher d’accéder au site internet de nouveau.

Créer un champ HoneyPot avec Symfony

Tout d’abord, nous allons commencer par le plus simple créer une classe CSS pour cacher le champ de notre formulaire.


.honeypot {
   display:none!important;
}

Ici, je nomme cette classe "honeypot" pour ce tutoriel, mais il serait probablement judicieux de lui donner un autre nom en production pour ne pas donner d’indication au robot.

Ensuite, nous allons créer une contrainte de validation spécifique qui pourra être utilisée pour détecter quand notre champ n’est pas nul.

Honeypot.php


<?php

namespace App\Validator;

use Symfony\Component\Validator\Constraints\IsNull;

/**
 * @Annotation
 * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
 */
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class HoneyPot extends IsNull
{
    public const HONEYPOT = 'ya6jA8ygGji0QS4Ga04cqUF3h33pcFQrB352sxT5O5kraolmHg';

    protected static $errorNames = [
        self::HONEYPOT => 'HONEYPOT',
    ];
}

HoneyPotValidator.php


<?php

namespace App\Validator;

use App\Event\HoneyPotEvent;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\IsNullValidator;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

class HoneyPotValidator extends IsNullValidator
{
    public function __construct(private EventDispatcherInterface $eventDispatcher)
    {
    }

    public function validate(mixed $value, Constraint $constraint)
    {
        if (!$constraint instanceof HoneyPot) {
            throw new UnexpectedTypeException($constraint, HoneyPot::class);
        }

        if (null !== $value) {
            $this->context->buildViolation($constraint->message)
                ->setParameter('{{ value }}', $this->formatValue($value))
                ->setCode(HoneyPot::HONEYPOT)
                ->addViolation();

            $this->eventDispatcher->dispatch(new HoneyPotEvent());
        }
    }
}

Cette contrainte va hériter de la contrainte IsNull, car elle a le même comportement, mais avoir une contrainte distincte nous permettra de déclencher un événement spécifique quand cette contrainte sera déclenchée.

Ensuite, nous allons créer un champ de formulaire spécifique.

HoneyPotType.php


<?php

namespace App\Form\Type;

use App\Validator\HoneyPot;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\OptionsResolver\OptionsResolver;

class HoneyPotType extends EmailType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            "mapped" => false,
            "required" => false,
            "row_attr" => [
                'class' => "honeypot"
            ],
            "constraints" => [
                new HoneyPot()
            ]
        ]);
    }
}

Notre champ HoneyType par défaut n’est pas requis ni relié à un champ de l’entité sur laquelle le formulaire est basé. Noter que ce champ utilise par défaut la classe CSS défini plus haut pour le masquer complétement ainsi que la contrainte HoneyPot que nous venons de créer. Enfin, ce champ va hériter de EmailType, car utilisé un champ email est un bon choix pour un honeypot notamment dans le cas d’un formulaire de contact.

Nous allons maintenant paramétrer un fichier log spécifique qui enregistrera l’adresse IP du robot malveillant avec monolog.

Si monolog n’est pas installé sur votre projet installez-le avec cette commande :


composer require symfony/monolog-bundle

Une fois monolog installé rendez-vous dans le fichier config/packages/monolog.yaml.

Vous pourrez alors y ajouter le code suivant au début du fichier :


monolog:
    channels:
        - deprecation
        - honeypot
    handlers:
        honeypot:
            type: stream
            path: "%kernel.logs_dir%/honeypot.log"
            level: alert
            channels: honeypot

Ce code permet de mettre en place un fichier log spécifique honeypot.log qui stockera les adresses IP des robots et sera lu par Fail2Ban pour procéder au bannissement.

Enfin, nous allons créer l’événement et le subscriber nécessaire pour enregistrer l’ip du robot malveillant dans les logs.

HoneyPotEvent.php


<?php

namespace App\Event;

class HoneyPotEvent
{
}

HoneyPotSubscriber.php


<?php

namespace App\EventSubscriber;

use App\Event\HoneyPotEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;

class HoneyPotSubscriber implements EventSubscriberInterface
{
    public function __construct(private LoggerInterface $honeypotLogger, private RequestStack $requestStack)
    {
    }

    public function onHoneyPotEvent(HoneyPotEvent $event): void
    {
        $request = $this->requestStack->getMainRequest();
        $ip = $request->getClientIp();

        $this->honeypotLogger->alert("BAN HoneyPot - " . $ip);
    }

    public static function getSubscribedEvents(): array
    {
        return [
            HoneyPotEvent::class => 'onHoneyPotEvent',
        ];
    }
}

Veuillez noter que le nom de la variable $honeypotLogger n’est pas choisi au hasard, car il permet d’utiliser spécifiquement la chaine honeypot défini dans le fichier de configuration de monolog.

Vous pouvez dès à présent tester le bon fonctionnement du HoneyPot en ajoutant ce champ à un formulaire et en simulant le comportement d’un spambot. Vous pouvez simuler ce comportement en utilisant la console de développement de votre navigateur préféré, puis en retirant la classe qui masque le champ et en y rentrant une valeur puis en validant le formulaire.

Après avoir réalisé cette opération, vous devriez voir dans le fichier var/log/honeylog.log une entré de ce type :


[2023-02-04T14:31:26.173249+01:00] honeypot.ALERT: BAN HoneyPot - 172.0.0.0 [] []

Nous en avons maintenant fini avec Symfony, nous allons maintenant configurer Fail2ban pour bloquer les spambots repérés.

Bannir l’IP avec Fail2Ban

Fail2Ban est un outil qui lit les logs d’un site, y repère les actions malveillantes commises à l’aide de filtre prédéfini et utilise le firewall du serveur pour interdire l’accès du serveur à l’IP associé cette action malveillante.

Commençons tout d’abord par installer Fail2Ban sur le serveur en exécutant cette commande :


sudo apt-get install fail2ban

Ensuite, nous devons créer un fichier de configuration local à fail2ban et copiant le fichier de configuration par défaut :


sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Rendez-vous ensuite dans le fichier jail.local, dans ce fichier, vous pourrez configurer fail2ban en activant différentes prisons prédéfinies en définissant les paramètres.

Vous pourrez par exemple activer la prison sshd, qui bloquera les adresse IP d’où proviennent des attaque brute force, en ajoutant la ligne d’activation dans la section dédiée.


[sshd]
enabled = true

Pour bloquer les adresses malveillantes repérées via notre système de HoneyPot, nous allons devoir créer notre propre prison. Pour ce faire ajouter ceci à la configuration :


[honeypot]
enabled = true
port = http,https
filter = honeypot # Notre filtre personnalisé
logpath = /full/path/to/honeypot.log # Chemin vers le fichier honeypot.log
maxretry = 1 # Nombre de tentative avant de bloquer une IP
findtime = 120 # Temps défini pour atteindre le nombre maximum de tentatives
bantime = 86400 # Temps pendant lequel l’IP est banni

Ensuite, nous devons définir un filtre personnalisé qui reconnaîtra nos logs à l’aide d’une expression régulière.

Pour créer notre filtre, créer le fichier /etc/fail2ban/filter.d/honeypot.conf et entrer dans ce fichier le contenu suivant :


[Definition]
failregex = honeypot.ALERT: BAN HoneyPot - <HOST>

Maintenant pour que la nouvelle configuration soit active, il faut redémarrer le service fail2ban avec cette commande :


systemctl restart fail2ban

Vous pouvez ensuite tester si notre nouveau filtre fonctionne avec la commande suivante :


sudo fail2ban-regex "[2023-02-04T14:31:26.173249+01:00] honeypot.ALERT: BAN HoneyPot - 172.0.0.0" honeypot

Si vous obtenez le résultat suivant le filtre fonctionne :


Lines: 1 lines, 0 ignored, 1 matched, 0 missed

Vous pouvez également voir les prisons actives en tapant cette commande :


sudo fail2ban-client status

Si honeypot figure parmi la liste des prisons affichées alors votre protection est active.

Voilà vous avez désormais un système permettant de repérer les spambots qui s’attaque à vos formulaires et de les bloquer automatiquement.

Publié le 8 févr. 2023, 13:28
Yann-Élias Bellagnech
Yann-Élias Bellagnech
Développeur Web
Passionné par le développement web, j'accompagne les entreprises, les agences et les indépendants dans la création de site internet de qualité.