J'ai écrit un CMS pour mes besoins personnels et je cherche à protéger le formulaire de connexion contre les attaques par force brute.

L'objectif est de répondre aux différents cas possibles d'attaque par force brute, en pénalisant le moins possible les utilisateurs.

Je me suis principalement basé sur les discussions suivantes sur stackoverflow The definitive guide to form-based website authentication et What is the best Distributed Brute Force countermeasure?.

La protection contre les attaques par force brute est gérée par une extension (plugin). Ce plugin est destiné à limiter le nombre de tentatives de connexion. Les autres mesures de protection (ex. : taille minimale/niveau de complexité du mot de passe) sont gérées ailleurs.

Le CMS s'appuie sur une architecture MVC des plus classiques. Les extensions fonctionnent avec une approche Publish-subscribe.

Principes de fonctionnement

sessionservice.class.php : service de connexion/déconnexion.

install.php : script exécuté lors de l'installation de l'extension

initialize.php : script exécuté lors de l'initialisation de l'extension (à chaque chargement de page)

L'extension répond à 3 événements :
  • before_login : après soumission du formulaire par l'utilisateur, avant vérification du nom d'utilisateur/mot de passe
  • after_login : l'utilisateur est identifié, avant création de la session
  • login_failed : après une tentative de connexion infructueuse


2 tables sont créées dans la base de données :
  • failed_login : tentatives de connexion infructueuses
  • recognized_ip : whitelist qui permet d'associer des utilisateurs avec des adresses IP utilisées pour se connecter.



before_login :

Cas n°1 : plusieurs tentatives avec le même nom d'utilisateur (que celui-ci existe ou non) :
Blocage temporaire à partir du 2ème échec.
La durée de blocage est multipliée par 2 à chaque échec, avec une durée maximale qui est paramétrable.

Cas n°2 : plusieurs tentatives avec la même adresse IP (peu importe le nom d'utilisateur) :
Blocage temporaire à partir du 2ème échec
La durée de blocage est multipliée par 2 à chaque échec, avec une durée maximale qui est paramétrable.

Cas n°3 (destiné aux attaques distribuées) : nombre total de tentatives sur une certaines période (paramétrable) dépassant un certain seuil (également paramétrable) :
Si l'IP est reconnue (présente dans recognized_ip et associée à l'utilisateur), on laisse passer. Sinon, après la troisième tentative sur la même IP, blocage avec une durée maximale.

Si on ne se trouve dans aucun de ces cas, on continue et le formulaire de connexion est affiché.

after_login :
Suppression des tentatives de connexion infructueuses
Ajout du couple nom utilisateur/ adresse IP à dans la table recognized_ip (et suppression des données anciennes)

login_failed:
Enregistrement de l'échec de connexion dans la table failed_login.

Le travail n'est pas terminé : Il faudrait également protéger le formulaire de réinitialisation du mot passe, gérer les adresses IPv6, etc.

Je voudrais avoir votre avis sur mon algorithme et mon code. Les résultats des tests me semblent satisfaisants. Mais je suis loin d'être un expert en sécurité.