L’attaque par force brute, ça n’arrive pas qu’aux autres

Pendant de nombreuses années, j’ai hébergé tous mes sites sur l’hébergement mutualisé d’OVH. Autant dire que tant que mon site fonctionnait, je ne me souciais pas du reste, et il était extrêmement rare que j’aille faire un tour dans les logs.
Cette habitude a bien changé depuis que je suis passé sur serveurs dédiés, et j’ai eu droit à quelques surprises.

Clés

Ainsi, il y a de cela un peu plus d’un mois, alors que je venais de passer une nouvelle version d’un site en production sur un serveur dédié, j’ai décidé d’aller faire un tour dans les logs d’Apache, juste pour voir. Surprise. Depuis plusieurs heures, un petit malin tentait de trouver le mot de passe pour accéder à phpMyAdmin.
Il s’agissait d’une attaque par force brute assez bidon, en testant les mots de passe suivants: 1, 11, 111, 1111, 11111, 2, 22, 222, … Autant dire qu’il avait un peu de temps avant de le trouver. C’est parfois un peu plus évolué, avec des attaques de type dictionnaire, où seulement certains mots de passe sont testés. Un exemple des logs pour une telle attaque ci-dessous. On remarque directement qu’il tente toujours de se connecter avec l’utilisateur root, et qu’il y a environ 20 tentatives par seconde. Je ne vous mets pas les 200 000 lignes de logs, mais ça a duré comme ça pendant deux heures.

[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?pma_username=root&pma_password=1q2w3e&server=1&lang=de-utf-8&convcharset=iso-8859-1 HTTP/1.1" 302 1202 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?lang=de&collation_connection=utf8_general_ci&token=13e98ef0f7e3599f220ffad5bc4d04ba&phpMyAdmin=3fdknnamoeq64tteu98dvr2kka4o3dlj HTTP/1.1" 200 7449 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?pma_username=root&pma_password=1q2w3e4r&server=1&lang=de-utf-8&convcharset=iso-8859-1 HTTP/1.1" 302 1146 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?lang=de&collation_connection=utf8_general_ci&token=719a9f60b6fe3a89c57d47695fc0bb02&phpMyAdmin=1fl4uq1lmubpj5ovtdbejofkavrbrlvu HTTP/1.1" 200 7449 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?pma_username=root&pma_password=r00t&server=1&lang=de-utf-8&convcharset=iso-8859-1 HTTP/1.1" 302 1150 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?lang=de&collation_connection=utf8_general_ci&token=7a505615a2e0fe273b4013af14d849ea&phpMyAdmin=jlcq464mto30godqs08lqqh5168adn3c HTTP/1.1" 200 7449 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?pma_username=root&pma_password=beheerder&server=1&lang=de-utf-8&convcharset=iso-8859-1 HTTP/1.1" 302 1160 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?lang=de&collation_connection=utf8_general_ci&token=2da01ee69ec2cf903189989727e00585&phpMyAdmin=p6aq2pelegrndd2n270j9kmcr06k7m26 HTTP/1.1" 200 7449 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?pma_username=root&pma_password=r00t1&server=1&lang=de-utf-8&convcharset=iso-8859-1 HTTP/1.1" 302 1146 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?lang=de&collation_connection=utf8_general_ci&token=328a6d19707b890edd214e75c6b25528&phpMyAdmin=953gj7li1p0b76fl6558bol2hfrrkghn HTTP/1.1" 200 7449 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?pma_username=root&pma_password=genius&server=1&lang=de-utf-8&convcharset=iso-8859-1 HTTP/1.1" 302 1148 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?lang=de&collation_connection=utf8_general_ci&token=e895d2bbd972df0d306cdd850683ef28&phpMyAdmin=h2rdle1qk667ahi4tr5f432ft77fgt3f HTTP/1.1" 200 7449 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?pma_username=root&pma_password=r0ot&server=1&lang=de-utf-8&convcharset=iso-8859-1 HTTP/1.1" 302 1146 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?lang=de&collation_connection=utf8_general_ci&token=75fbda0bc6266389b95ef094a5ed1674&phpMyAdmin=uav70qum6oclls3r8kgp92t1bk4bl2gu HTTP/1.1" 200 7449 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?pma_username=root&pma_password=ro0t&server=1&lang=de-utf-8&convcharset=iso-8859-1 HTTP/1.1" 302 1152 "-" "-"
[12/Aug/2013:13:00:27 +0200] "GET /phpmyadmin/index.php?lang=de&collation_connection=utf8_general_ci&token=8aaa1314543c9253648e08eea7fa665e&phpMyAdmin=t71tn2jo9cdiju9nm7gvtm90qdc2cg2u HTTP/1.1" 200 7449 "-" "-"
[12/Aug/2013:13:00:28 +0200] "GET /phpmyadmin/index.php?pma_username=root&pma_password=r00t_1&server=1&lang=de-utf-8&convcharset=iso-8859-1 HTTP/1.1" 302 1148 "-" "-"
[12/Aug/2013:13:00:28 +0200] "GET /phpmyadmin/index.php?lang=de&collation_connection=utf8_general_ci&token=b69084956c5486976e705e0d3ce3ccb2&phpMyAdmin=0im61sschi84usandq72thr6ivlbq0t2 HTTP/1.1" 200 7449 "-" "-"
[12/Aug/2013:13:00:28 +0200] "GET /phpmyadmin/index.php?pma_username=root&pma_password=r00t123&server=1&lang=de-utf-8&convcharset=iso-8859-1 HTTP/1.1" 302 1146 "-" "-"
[12/Aug/2013:13:00:28 +0200] "GET /phpmyadmin/index.php?lang=de&collation_connection=utf8_general_ci&token=956bcf657764260f03fd739096a34b99&phpMyAdmin=1tcav6dqd99g6eeqf5g6ld87vada81pq HTTP/1.1" 200 7449 "-" "-"
[12/Aug/2013:13:00:28 +0200] "GET /phpmyadmin/index.php?pma_username=root&pma_password=1q2w3e4r5t&server=1&lang=de-utf-8&convcharset=iso-8859-1 HTTP/1.1" 302 1160 "-" "-"
[12/Aug/2013:13:00:28 +0200] "GET /phpmyadmin/index.php?lang=de&collation_connection=utf8_general_ci&token=0cf6f4b633e90cbc81bc932bde083ebe&phpMyAdmin=0u5nsk6fu98d6mlpiau7hv9ofnca9so7 HTTP/1.1" 200 7449 "-" "-"
[12/Aug/2013:13:00:28 +0200] "GET /phpmyadmin/index.php?pma_username=root&pma_password=1q&server=1&lang=de-utf-8&convcharset=iso-8859-1 HTTP/1.1" 302 1150 "-" "-"
[12/Aug/2013:13:00:28 +0200] "GET /phpmyadmin/index.php?lang=de&collation_connection=utf8_general_ci&token=f846034aeb42cd3cef70e79992671bac&phpMyAdmin=ojtcfdnlvhqahm5j6j5i0pe1jgcm9quh HTTP/1.1" 200 7449 "-" "-"

J’ai donc fait en sorte de ne plus subir cette attaque, et je vais vous expliquer comment un peu plus bas. Mais après avoir fait cela, j’ai remarqué d’autres choses dans les logs. Quelqu’un essayait d’accéder à monsite.tld/admin, et bien d’autres du genre alors que ces urls n’existent pas. Soit des noms génériques comme /admin, soit des noms de scripts bien connus et fort utilisés (éventuellement accompagnés de numéro de version).

[Sun Aug 18 16:29:14 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/phpmyadmin
[Sun Aug 18 16:29:15 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/phpMyAdmin
[Sun Aug 18 16:29:16 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/pma
[Sun Aug 18 16:29:17 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/mysql
[Sun Aug 18 16:29:17 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/sql
[Sun Aug 18 16:29:18 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/PMA
[Sun Aug 18 16:29:18 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/admin
[Sun Aug 18 16:29:19 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/dbadmin
[Sun Aug 18 16:29:20 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/myadmin
[Sun Aug 18 16:29:20 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/db
[Sun Aug 18 16:29:21 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/sqlmanager
[Sun Aug 18 16:29:21 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/phpmyadmin2
[Sun Aug 18 16:29:22 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/phpMyAdmin2
[Sun Aug 18 16:29:22 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/phpMyAdmin-2
[Sun Aug 18 16:29:23 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/php-my-admin
[Sun Aug 18 16:29:24 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/phpMyAdmin-3.5.8-rc1
[Sun Aug 18 16:29:24 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/phpMyAdmin-4.0.0-rc1
[Sun Aug 18 16:29:25 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/phpMyAdmin-3.5.7-1
[Sun Aug 18 16:29:25 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/phpMyAdmin-3.5.7
[Sun Aug 18 16:29:26 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/phpMyAdmin-3.5.6
[Sun Aug 18 16:29:27 2013] [error] [client ...ip...] File does not exist: /var/www/default-site/phpMyAdmin-3.5.5
... Et ça continue encore longtemps

Gérer mon propre serveur dédié m’aura au moins permis de réaliser que les attaques, ça n’arrive pas qu’aux autres. C’est pourquoi j’ai décidé de partager quelques petites astuces très simples mais qui permettent d’éviter en grande partie ce genre de désagréments.

Changez les chemins prévisibles

La manière la plus simple de résoudre ce problème de tentative d’accès à phpMyAdmin serait tout simplement de ne pas l’installer. Ça enlève tous les risques, mais c’est radical. On va donc essayer d’éviter cette solution.

Jouer sur les IPs pour donner l’accès à phpMyAdmin à seulement certaines personnes. Efficace, mais potentiellement pénible à mettre en place.

Bannir les IPs qui font ces trop nombreuses tentatives de connexions. Mais comme vous n’avez pas envie de jouer à faire le gendarme toute la journée, et gérer ça manuellement, vous utiliserez sans doute fail2ban qui va permettre de bannir les IPs qui font trop de tentatives de connexions. Tout est à peu près configurable, et il existe des filtres pour de nombreux services comme Apache, SSH, Courier, … C’est sans doute la meilleure solution, mais on a parfois besoin de quelque chose de plus rapide à mettre en place, en attendant d’avoir le temps de faire mieux.

Et si votre phpMyAdmin était non pas accessible sur votresite.tld/phpMyAdmin mais sur votresite.tld/jemangeunepomme ? Il y a très peu de chance que l’attaquant tente cette adresse. C’est simple et ça prend moins de trente secondes à faire. Certes, ce n’est pas la solution parfaite, mais si on regarde le rapport temps à mettre en place et résultat, ça parait pas mal.

Pour changer le chemin d’accès à phpMyAdmin, éditez simplement le fichier /etc/phpmyadmin/apache.conf.

Alias /phpmyadmin /usr/share/phpmyadmin

Changez simplement l’Alias, par exemple

Alias /jemangeunepomme /usr/share/phpmyadmin

Il ne reste alors plus qu’à recharger Apache.

/etc/init.d/apache2 reload

Interdisez l’utilisateur root

On a vu que le nom d’utilisateur utilisé pour se connecter est root. C’est normal, c’est le premier utilisateur de base créé lorsqu’on installe notre base de données. Il est certainement possible de changer le nom de cet utilisateur, ou bien de le supprimer (après avoir pensé à créer un nouveau compte ayant tous les droits), mais le plus simple est certainement d’interdire d’utiliser root dans phpMyAdmin.

Pour cela, éditez le fichier /etc/phpmyadmin/config.inc.php, pour y avoir ceci:

$cfg['Servers'][$i]['AllowRoot'] = FALSE;

Attention, cela ne concernera que phpMyAdmin. Si votre serveur SQL est accessible depuis l’extérieur, vous risquez quand même d’avoir des ennuis. Si seul votre serveur utilise la base de données, autorisez uniquement les connexions à celle-ci à partir de localhost.

Tant qu’on y est, je vous conseille aussi de désactiver la connexion SSH avec le compte root. On évitera les mêmes problèmes. Et vous pouvez en plus changer le port pour ne pas avoir le port par défaut. Ainsi, vous devriez être tranquille. Vous pouvez faire tout cela en éditant le fichier /etc/ssh/sshd_config:

Port 3113
PermitRootLogin no

Protégez aussi vos scripts maison

On termine par une dernière astuce, qui là aussi n’est pas une solution idéale, mais a l’avantage d’être mise en place en moins de deux minutes, et permet d’obtenir un résultat généralement assez satisfaisant.

Dans vos scripts, lorsqu’un formulaire de connexion est disponible, et que celui-ci est validé, vous vérifiez identifiant et mot de passe avec ce qu’il y a dans la base de données. Si le tout est correct, le visiteur est connecté, sinon il obtient un message d’erreur et peut retenter de se connecter.

Dans un monde idéal, vous mettez en place un système qui empêchera de se connecter (ou obligera à attendre un certain temps) après un certain nombre de tentatives échouées. Bien que ce ne soit pas difficile à mettre en place, une solution plus simple est généralement acceptable.

On le voyait dans l’exemple concernant phpMyAdmin, il y avait environ 20 tentatives par secondes. Pour avoir une protection suffisante, il faudrait pouvoir diminuer ce nombre, et une des solutions pour cela est d’allonger le temps de réponse. Si l’attaquant doit attendre deux secondes au lieu de 100 millisecondes pour savoir s’il a entré le bon mot de passe, il lui faudra beaucoup plus longtemps pour avoir un succès.

Il ne reste alors plus qu’à mettre ça en pratique. Lorsqu’on tente de se connecter et que les identifiants sont incorrects, on allonge le temps de réponse, par exemple de une ou deux secondes.

if (connect($user, $password)) {
    // Password ok
}
else {
    // Password not ok: wait one second
    sleep(1);
}

Évidement, et je vous l’ai déjà dit, ce n’est pas parfait. L’attaquant peut très bien analyser le temps de réponse et considérer que si celui-ci est trop long, le mot de passe est incorrect, et il ne devra alors pas attendre une seconde complète et s’arrêtera avant. Cependant, cette solution rapide est généralement suffisante, et ne gêne pas particulièrement les utilisateurs étant donné que le léger délai est uniquement présent lorsque les identifiants sont incorrects. On pourrait imaginer une seconde d’attente dans tous les cas si on veut contrer l’analyse du temps de réponse. Mais veut-on vraiment imposer ça à nos utilisateurs ?

Et vous, quelles sont vos techniques pour vous protéger un minimum face à ces attaques ? Ou êtes vous comme moi il y a quelques temps, et vous ne vous en souciez pas ?

facebooktwittergoogle_pluspinterestlinkedintumblrmailfacebooktwittergoogle_pluspinterestlinkedintumblrmail

Un commentaire

  1. Très bon article. Il est aussi possible, si vous avez un serveur dédié, d'ajouter une règle Deny dans la configuration Apache pour interdire la connexion à certaines adresses IP. (http://httpd.apache.org/docs/current/mod/mod_access_compat.html)
    Reply
  2. Bonjour,
    Très bon article, merci à toi.
    Dans le cas d'un formulaire de connexion à un espace membre, concernant le temps d'attente suite à une combinaison fausse (mdp/login), la question n'est pas "dois-je|puis-je l'imposer à mes utilisateurs ?" sachant que le temps de revenir sur la page et taper de nouveau sa combinaison prendra bien plus d'une seconde !
    Donc on peut se faire plaisir !
    La question que je me pose maintenant, peut-on mettre en place un système similaire à d'autres endroits ?
    Formulaires de contact par exemple ? (Quoi je ne l'ai pas déjà !?)
    Bien amicalement, Yann.
    Reply
  3. Attention, le sleep(1) sur une page de connexion peut aussi être utilisé pour une attaque de type déni de service (en inondant le login de requête).
    Reply

Laisser un commentaire.