- Modifié
Cet article provient du Blog de @Hardware, j'espere qu'il m'en voudra pas d'essayer de le mettre à jour.
Merci à lui pour cette base.
Se protéger des attaques de type HTTP-FLOOD avec Ossec et Iptables
Aujourd'hui nous allons voir comment bloquer les attaques DDOS de type HTTP-FLOOD (couche 7 du modèle OSI) avec Ossec et Iptables. Il existe différents types d'attaques DDOS, de taille et de complexité différente.
Celle dont on va se prémunir aujourd'hui est une des plus simple. L'attaquant a juste besoin d’inonder le serveur cible avec de simples requêtes HTTP(S) GET/POST jusqu'à que le serveur ne puisse plus répondre à la charge. On peut facilement faire tomber un site web mal protégé avec un simple outil de bench.
La première chose qu'on peut faire contre ce genre d'attaque, c'est d'appliquer un rate limiting au niveau du serveur web, ça a l'avantage d'être simple à mettre en place et c'est plutôt efficace.
Par exemple avec Nginx :
Connexions maximum par ip
limit_conn_zone $binary_remote_addr zone=limit_per_ip:10m;
limit_conn limit_per_ip 20;
Nombre de requêtes/s maximum par ip
limit_req_zone $binary_remote_addr zone=allips:10m rate=200r/s;
limit_req zone=allips burst=200 nodelay;
Je vous conseille la lecture de cet article qui détail le sujet :
https://www.abyssproject.net/2014/06/bloquer-les-attaques-ddos-nginx/
Comme j'utilise Ossec depuis quelques années, je me dis qu'il peut être intéressant de l'utiliser pour se prémunir de ce genre d'attaque. Ossec s'installe très rapidement et est plutôt léger, selon Daniel Cid son créateur, il peut tourner sur de très petites configurations (moins de 128Mo de RAM) tout en étant efficace quand il est bien configuré.
Voici la procédure pour l'installer sur une Debian Jessie :
cd /tmp
wget 'https://bintray.com/artifact/download/ossec/ossec-hids/ossec-hids-2.8.3.tar.gz'
tar xzf ossec-hids-2.8.3.tar.gz && cd ossec-hids-2.8.3/
apt-get install libgcrypt20-dev libgnutls28-dev
Lancer l'installateur puis démarrage du service
./install.sh
systemctl enable ossec && systemctl start ossec
Je ne vais pas détailler le fonctionnement d'Ossec dans cet article, ce n'est pas le but mais vous pouvez toujours aller voir mon tutoriel sur mondedie.fr qui explique comment le configurer.
Tuto [Obsolète]
Discussion du Tuto
Bon on va commencer par appliquer les règles iptables. Donc l'idée de base est simple, on veut limiter le nombre de requêtes TCP sur un port particulier, donc 80/443 dans notre cas.
/sbin/iptables -N http-flood
/sbin/iptables -A INPUT -p tcp --syn --dport 80 -m connlimit --connlimit-above 1 -j http-flood
/sbin/iptables -A INPUT -p tcp --syn --dport 443 -m connlimit --connlimit-above 1 -j http-flood
/sbin/iptables -A http-flood -m limit --limit 10/s --limit-burst 10 -j RETURN
/sbin/iptables -A http-flood -m limit --limit 1/s --limit-burst 10 -j LOG --log-prefix "HTTP-FLOOD "
/sbin/iptables -A http-flood -j DROP
Il faut comprendre les règles au-dessus de la manière suivante :
- Si il y a plus d'une connexion TCP de manière simultanée (--connlimit-above) provenant d'une même IP,
- alors on lui applique le flag http-flood.
- Tant qu’il y a moins de 10 syn/seconde on laisse passer sinon iptables log le paquet et drop tout le reste.
Pour expliquer un peu plus en détail, en fait le paramètre --limit-burst 10 permet de déclencher le rate limit (--limit 10/s) uniquement si ce taux est dépassé initialement. Donc 10 req simultanées au départ puis iptables surveille que la limite de 10 req/s ne soit pas dépassée. Si c'est le cas, il log puis drop. Le compteur "limit-burst" se réinitialise à chaque fois que la limite (--limit 10/s) n'est pas dépassée.
J'ai mis le module "limit" sur la règle qui log les paquets pour ne pas que le fichier de log ne grossisse trop vite et ainsi éviter une saturation de l'espace disque. Ce n'est pas très utile vu qu'on va bannir la source juste après avec Ossec mais ça coûte rien de mettre une limite à ce niveau là.
Pour ceux qui utilisent Docker, il faut modifier l'ordre d'application des règles en remplaçant -A (append) par -I (insert), iptables parcourant les règles de haut en bas, il faut obligatoirement mettre les deux premières règles en haut de la pile, avant celles de docker, sinon elles ne s’appliqueront pas.
Il faut aussi penser à changer la chaîne par lequel le paquet va passer, avec Docker il s'agit de la chaîne FORWARD. Les règles suivantes fonctionnent bien chez moi avec un conteneur nginx en reverse proxy :
/sbin/iptables -N http-flood
/sbin/iptables -I FORWARD -p tcp --syn --dport 80 -m connlimit --connlimit-above 1 -j http-flood
/sbin/iptables -I FORWARD -p tcp --syn --dport 443 -m connlimit --connlimit-above 1 -j http-flood
/sbin/iptables -A http-flood -m limit --limit 10/s --limit-burst 10 -j RETURN
/sbin/iptables -A http-flood -m limit --limit 1/s --limit-burst 10 -j LOG --log-prefix "HTTP-FLOOD "
/sbin/iptables -A http-flood -j DROP
Bon on passe maintenant à la configuration des règles d'Ossec, ajouter le contenu suivant dans le fichier /var/ossec/rules/local_rules.xml :
<rule id="100042" level="5">
<if_sid>4100</if_sid>
<match>HTTP-FLOOD</match>
<description>Layer 7 HTTP Flood Attempt</description>
</rule>
<rule id="100043" level="5" frequency="10" timeframe="10">
<if_matched_sid>100042</if_matched_sid>
<same_source_ip />
<options>alert_by_email</options>
<description>Multiple Layer 7 HTTP Flood Attempt</description>
<group>attack,</group>
</rule>
La première règle se déclenche dès qu'elle rencontre le terme HTTP-FLOOD dans les logs d'iptables (règle 4100, voir : rules/firewall_rules.xml).
La seconde règle se déclenche uniquement si la première est match plus de 20 fois (donc 20 événements HTTP-FLOOD dans les logs) pendant un laps de temps de 10 secondes.
Je sais pas si c'est très clair, si on récapitule ça donne ça :
Iptables limite à 10 connexions simultanées sur les ports 80/43
Si la limite est dépassé alors flag du paquet avec le marqueur HTTP-FLOOD
Puis ajout dans les logs de l'IP incriminée avec le flag HTTP-FLOOD
Le nombre initial d'event dans les logs est de 10 (limit-burst) puis 1/s (limit)
Donc 10 secondes d'attaque (timeframe) = 20 events HTTP-FLOOD dans les logs
Frequency = nbEvents / 2, donc 20 / 2 = 10
Il ne reste plus qu'à configurer notre active-response dans /var/ossec/etc/ossec.conf :
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100043</rules_id>
<timeout>3600</timeout>
<repeated_offenders>360,720,1440</repeated_offenders>
</active-response>
Avec cette AR, l'attaquant est banni via iptables (chaînes INPUT+FORWARD) pendant une durée initiale d'une heure. Si il répète son attaque, il sera de nouveau ban pendant 6H puis 12H puis 24H. La règle 100043 correspond à celle que nous avons créé juste au dessus.
Il est aussi possible de mettre en place un ban permanent avec cette commande :
<command>
<name>DropForLife</name>
<executable>firewall-drop.sh</executable>
<expect>srcip</expect>
<timeout_allowed>no</timeout_allowed>
</command>
Puis en la précisant dans l'active response à la place de la commande firewall-drop.
Je ne recommande pas de bannir une IP de manière permanente, on est jamais à l'abris que les adresses IP sources soient forgées aléatoirement, l'attaquant peut prendre par exemple les adresses des serveurs de google et ainsi bloquer de manière permanente le référencement d'un site web par exemple.
Voila la protection est en place, pensez à faire un petit reboot d'Ossec pour qu'il prenne en compte les modifications :
systemctl restart ossec
J'ai par habitude d'utiliser le paquet iptables-persistent pour sauvegarder/restaurer et faire persister les règles après un redémarrage du serveur.
Sur Jessie :
apt-get install iptables-persistent
systemctl enable netfilter-persistent
systemctl start netfilter-persistent
Avant d'appliquer les règles anti-ddos, je fais toujours un backup des règles existantes :
iptables-save > /docker/iptables/default-docker.rules
Couplé avec un petit script pour revenir en arrière si les nouvelles règles posent soucis :
#!/bin/bash
# Suppression de toutes les règles iptables
/sbin/iptables -P INPUT ACCEPT
/sbin/iptables -P FORWARD ACCEPT
/sbin/iptables -P OUTPUT ACCEPT
/sbin/iptables -t nat -F
/sbin/iptables -t mangle -F
/sbin/iptables -F
/sbin/iptables -X
# Restauration des régles initiales de docker
/sbin/iptables-restore < /docker/iptables/default-docker.rules
/sbin/iptables-save > /etc/iptables/rules.v4
Au moins comme ça pas de danger, on peut revenir en arrière.
Maintenant que les règles iptables sont appliquées et Ossec configuré, on peut passer aux tests !
Accrochez vos ceintures, ça va secouer... ou pas
Pour effectuer un test, j'utilise un petit outil de benchmark de la fondation Apache, nommé Apache Bench
Bon c'est parti, on envoie la sauce :
Faites le test depuis une autre machine que celle que vous utilisez pour vous connecter sur votre serveur, ça paraît logique hein, mais je préfère le signaler quand même ^^
ab -n 2000 -c 100 http://domain.tld
Une fois la commande lancée, vous devriez voir des logs de ce type apparaître dans les fichiers de log système /var/log/{messages, syslog} :
hostname kernel: [xxx.xxx] HTTP-FLOOD IN=eth0 OUT=docker0 MAC=xx.xx.xx.xx SRC=xx.xx.xx.xx DST=172.17.0.2
LEN=60 TOS=0x00 PREC=0x00 TTL=52 ID=55338 DF PROTO=TCP SPT=39526 DPT=8000 WINDOW=29200 RES=0x00
SYN URGP=0
Si c'est le cas, ça veut dire que iptables fait le job. Il faut maintenant vérifier la réaction de Ossec. Regardez le fichier /var/ossec/logs/active-responses.log, normalement une ligne comme celle-ci est apparue :
/var/ossec/active-response/bin/firewall-drop.sh add - IP_BANNIE 1455541862.10638 100043
Voici une petite commande pour avoir la liste des adresses IP bannies :
iptables -L INPUT | tail -n +3
Voila, c'est une solution parmi tant d'autres, je pense qu'elle reste suffisante pour des attaques non sophistiquées de moyenne ampleur, en tout cas les scripts kiddies et autre attaquant du dimanche n'ont pas la moindre chance.
Pour des attaques plus complexes (IP forgées, botnets...etc), il existe des solutions plus efficaces mais aussi plus chers et plus compliquées à mettre en place mais là on sort un peu du cadre de cet article.