Aujourd’hui nous allons parler d’une mesure de sécurité très simple dans son approche. Nous voulons limiter le nombre d’essais de connexion pour un nom d’utilisateur. Le but est d’empêcher une attaque brute-force, où un attaquant qui connaît ou devine un nom d’utilisateur va essayer tous les mots de passe possibles. Bien que très peu sophistiquée, cette attaque est courante, par exemple elle serait à la source de la dévastatrice fuite de photos de personnalités en 2014. La mesure de protection est simple dans son concept, et nous allons voir, simple à mettre en place.
Choix de l’approche
Pour cette première approche, nous voulons empêcher plus de 3 tentatives de connexion avec le même nom d’utilisateur (ou adresse e-mail) en l’espace de 10 minutes. Ces valeurs semblent raisonnables point de vue utilisateur, et cela permet de rendre une attaque brute-force trop coûteuse en temps: au maximum, 432 mots de passe pourraient être testés en 24 heures. On pourrait vouloir une approche plus dure, qui bloque la connexion de l’utilisateur après X tentatives, mais alors il faut prévoir et mettre en place une procédure permettant à l’utilisateur légitime de reprendre possession de son compte. Sinon il sera facile à un attaquant de bloquer uns à uns tous les administrateurs ou tous les utilisateurs de notre plate-forme, ce qui est aussi destructeur!
Deuxième aspect important, nous voulons logguer les tentatives de connexion, afin de pouvoir monitorer et analyser ce qui se passe sur notre production, de détecter s’il y une activité suspecte. Pour une première version simple, nous allons utiliser une entité Doctrine, à défaut d’outils plus évolués. Cela sera suffisant pour une application web au traffic faible à modéré.
Code avec Symfony 4
Nous allons commencer par l’entité. Elle est relativement basique, on stocke la date de la tentative, le nom d’utilisateur envoyé, et l’adresse IP (pour détecter les attaques très basiques). Pas besoin de setters, l’entité est immutable, on ne va pas la modifier une fois construite.
Ne pas oublier de générer une migration Doctrine (bin/console make:migration
) et de l’exécuter une fois l’entité créée!
Pour la suite, nous partons du principe que votre authentification est gérée par un Guard, similaire à ce que génèrerais make:auth
du Symfony Maker. Dans notre exemple, on utilise « email » comme nom d’utilisateur, mais vous pouvez adapter facilement. Seules les méthodes getCredentials
et checkCredentials
sont modifiées, le code ci-dessous est commenté avec des explications:
Finalement, il reste à implémenter la méthode LoginAttemptRepository::countRecentLoginAttempts()
. Ici le critère est simple, avec un seul délai, libre à vous de faire quelque chose de plus évolué.
Bilan
Notre application est maintenant protégée! Libre à vous d’aller plus loin, avec par exemple des délais exponentiels (le compte serait bloqué 10 minutes, puis 20mn à la seconde violation…) ou alors avec une tâche qui supprimera automatiquement les données en base après 30 jours.
Rendez-vous la semaine prochaine, la série d’articles sur la Sécurité avec Symfony continue 🙂