Jouons avec les script-kiddies

Posted on mar. 18 juillet 2017 in adminsys

Depuis bien 10 ans, je gère des serveurs directement accessibles sur internet. J'ai commencé par auto-héberger quelques trucs pour moi quand j'étais étudiant sur un PC de récupération, avant d'en faire mon métier.

Aujourd'hui, j'auto-héberge toujours des outils/blog/projets perso pour le fun.

L'une des premières proccupations que l'on doit avoir quand on met un serveur sur le wild internet, c'est ça sécurisation. Il n'y a pas forcément besoin d'être expert en sécurité pour appliquer quelques règles minimales. On pense typiquement à la connexion SSH uniquement par clefs, quelques règles iptables, fail2ban, etc...

Je ne m'étendrais pas sur ces mesures nécessaires, mais plutôt sur un comportement très particulier de script kiddies et comment on peut s'amuser un peu avec eux (enfin, à leur dépend).

Je n'utilise pas Wordpress

Constatations

Commençons par le commencement : je n'utilise pas wordpress. Par contre, ça ne m'empèche pas de voir ce genre de chose dans mes logs de serveur web (Nginx pour moi) :

X.X.X.X - - [12/Jul/2017:16:23:34 +0200] "GET /wp-login.php HTTP/1.1" 404 408 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"
X.X.X.Y - - [12/Jul/2017:16:27:50 +0200] "GET /wp-login.php HTTP/1.1" 404 408 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"
X.X.X.Z - - [12/Jul/2017:16:32:17 +0200] "GET /wp-login.php HTTP/1.1" 404 408 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"
X.X.X.A - - [12/Jul/2017:16:35:37 +0200] "GET /wp-login.php HTTP/1.1" 404 408 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"
X.X.X.B - - [12/Jul/2017:16:51:58 +0200] "GET /wp-login.php HTTP/1.1" 404 408 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"

Décriptons vite fait pour ceux qui n'aurait jamais vu de log de serveur web : mon serveur web Nginx se voit demandé par plusieurs IP différentes de servir la page wp-login.php, mais comme elle n'existe pas renvoie une erreur 404.

Décryptage

Pourquoi ces logs ?

Parce que wordpress, dans sa configuration par défaut, utilise cette page wp-login.php pour authentifier les utilisateurs. D'autres pages propres à wordpress sont aussi demandées, toujours sans exister chez moi : wp-content, xmlrpc.php, etc...

Ces pages pouvant présenter des vulnérabilités sur d'anciennes version de Wordpress, si des apprentis hackeurs les trouvent, ils auront tôt fait de le vérifier et de s'approprier le site vulnérable.

Zip-Bomb

Origine

Suite à un article de Korben, adapter le concept de la Zip Bomb pour défendre son site web des scripts kiddies, je me suis mis en tête de l'appliquer sur un de mes serveurs.

Problème, je n'utilise pas du tout PHP, sur aucun des sites que j'héberge sur le serveur choisi.

Qu'à cela ne tienne, il doit bien y avoir moyen de faire quelque chose quand même avec la configuration du serveur web directement.

Explication

Le but du jeu est de faire crasher le logiciel ou carrément l'ordinateur du malheureux méchant qui voudrait accéder à la page wp-login.php.

Le principe de la Zip-Bomb est simple : on va donner à l'attaquant un petit fichier compressé de 10Mo (inofencif pour nous), mais qui une fois décompressé fait 10Go. Un petit ordre 1000, oui. La plupart du temps, les ordinateurs ne supporteront pas bien cette décompression par manque de RAM (qui à 10Go de RAM de libre ?) ou par manque de place disque.

Mise en place

Créons la zip bomb. Sur votre serveur linux faites :

dd if=/dev/zero bs=1M count=10240 | gzip > /srv/badrobots/10G.gzip

Et voilà.
Vérifiez, le fichier compressé fait environ 10Mo.

La suite se trouve dans la configuration nginx :

server {
    listen 80;
    listen [::]:80;
    server_name mon_super_nom_de_domaine.tld;
    ...
    location ~ (wp-login|wp-content|xmlrpc) {rewrite ^/.* /10G.gzip;}
    location /10G.gzip {alias /srv/badrobots/10G.gzip; more_set_headers 'Content-Type: text/html' 'Content-Encoding: gzip';}
    ...
}

Note : J'utilise ici le plugin nginx "more_set_headers" au lieu du simple "add_header". Dans notre cas, il faut changer le champ "Content-Type", et l'usage de la directive "add_header" ne fait que rajouter un header "Content-Type" supplémentaire. Et au final ça ne marchera pas, le client web ne prenant pas le bon header "Content-Type". Sur système type Debian, installez le package "nginx-extras" plutôt que les "nginx-light" ou "nginx-full".

Expliquons : pour toute requête contenant wp-login ou wp-content ou xmlrpc, on va ré-écrire la requête pour demander à se faire servir le path "/10G.gzip". Pour ce path particulier, on va servir le fichier qui nous intéresse et ajouter/modifier des headers HTTP. On modifie le "Content-Type" pour indiquer que c'est une page HTML si on ne fait pas ça, Nginx indique un type binaire et donc le client demandera juste à l'utilisateur où il veut télécharger le fichier. On ajoute le header "Content-Encoding" pour indiquer au navigateur qu'il faut qu'il décompresse ce qu'on lui a envoyé.

Pour faire court : pour toute requête contenant wp-login ou wp-content ou xmlrpc, on va plutôt leur servir la zip bomb.

Et voilà !

Résultats

Pour voir ce que cela fait, vous pouvez soit tentez par vous-même, en allant http://blog.etienne-magro.fr/wp-login.php, soit en lisant le blogpost de l'auteur de l'idée qui a fait les tests pour vous ;-)

En résumé, ça fait planté certains navigateurs.

Fail2ban

Ha oui, aussi, n'oubliez pas de mettre à jour vos filtres fail2ban.

En effet, là où avant, nginx répondait une erreur 404 Not Found, maintenant, il répond 200 en envoyant la zip bomb. Vos regex fail2ban pour wordpress ne vont plus fonctionner.

Les logs maintenant :

X.X.X.X - - [18/Jul/2017:18:20:09 +0200] "GET /wp-login.php HTTP/1.1" 200 10420385 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"

Récapitulons : non seulement on fait crasher l'ordinateur du script kiddie avec notre conf nginx, mais une fois crashé, on le ban avec fail2ban.

Qu'est-ce qu'on s'amuse :-)

Bonus

Bonus 1 : Mauvais nom de domaine

Petit bonus pour toutes les requêtes qui arrivent sur le serveur avec un mauvais nom de domain :

$ cat /etc/nginx/sites-enabled/default
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    location / {rewrite ^/.* /10G.gzip;}
    location /10G.gzip {alias /srv/badrobots/10G.gzip;more_set_headers 'Content-Type: text/html' 'Content-Encoding gzip';}
}

Bonus 2 : Règles plus complètes

Pour finir, quelques règles plus complètes qui prend aussi les phpmyadmin et 2-3 autres merdes.

Cadeau !

$ cat /etc/nginx/conf.d/zipbomb.conf
map $location $bad_location {
    default 0;
    ~*(?i)(wp-login|wp-content|wp-admin|wp-signup|xmlrpc|typo3|xampp|pma|(php)?[Mm]y[Aa]dmin) 1;
}

$ cat /etc/nginx/site-enabled/my-site.conf
server {
    listen 80;
    listen [::]:80;
    server_name mon_super_nom_de_domaine.tld;
    ...
    if ($bad_location) {rewrite ^/.* /10G.gzip;}
    location /10G.gzip {alias /srv/badrobots/10G.gzip;more_set_headers 'Content-Type: text/html' 'Content-Encoding: gzip';}
    ...
}