lid's stuff

HAProxy: mitiger l'impact de bots ou d'un petit DDoS

Je vais encore lui faire de la pub, mais HAProxy, c'est vraiment un logiciel génial…

Récemment, avec la fin des vacances, et l'actualité assez chargée sur laquelle je ne reviendrai pas, bien évidemment un paquet de bots ont commencé à gratouiller une des plateformes que j'administre (il s'agit d'une plateforme de blogs assez importante. Qui dit blog, dit blog de mamie, blog de geeks, blog de passionné de {put your passion here}, et bien évidemment: blogs politiques et religieux. Je vous laisse deviner les cibles de ces bots, en ces temps de fascismes et autres extrêmismes résurgents et régurgités un peu partout dans nos médias habituels).

Comportement des bots

Leur modus operandi est toujours le même: recharger en boucle un blog en particulier, pas toujours la même URL, et environ une dizaine de rechargements de la même URL par seconde, soit ~200 requêtes sur 20 secondes, puis se "calmer" un peu, et recommencer avec une autre URL. Le tout venant des quatres coins de la planète (IPs françaises, allemandes, russes, américaines, indiennes, etc, et derrière des machines allant du smartphone Android en passant par l'iPhone, des PCs de particuliers sous Windows©®, des serveurs chez Online ou OVH, des VMs Azure et Amazon, et même un routeur Mikrotik sous RouterOS - j'y reviendrai sûrement dans un prochain post d'ailleurs, concernant cette machine).

Bien que leur trafic n'impactait pas les performances de la plateforme, je n'aime pas trop avoir une armée de bots au portillon. Déjà parceque ça fait désordre, et aussi parceque cela pouvait pourrir les stats utilisateurs de la plateforme, et potentiellement impacter les performances à la rentrée.

Iptables: première approche

Ma première réaction fût bien évidemment de parser mes logs, repérer les IPs qui effectuaient ces requêtes en boucles et les TARPIT pour faire bonne mesure. Sur le coup, ce fût efficace, mais ils devaient disposer de plus de moyens que ce que je pensais en face: pour une IP bannie, deux autres se repointaient. Mais ça m'a cependant laissé le temps de mieux analyser leur comportement, toujours similaire.

Sans compter que mes tables iptables se remplissaient bien vite, ce qui peut aussi impacter les performances à plus ou moins court terme, et qu'en plus une grande partie des IPs "attaquantes" appartenant à des blocs d'IPs dynamiques de FAIs européens, je risquais aussi d'impacter des utilisateurs légitimes de la plateforme (qui auraient pû récupérer une IP bannie au prochain refresh de leur IP par leur FAI).

Pour le coup, et vu la durée de "l'attaque" (pour autant qu'on puisse appeler ça une attaque), iptables n'était pas mon pote…

Une autre approche aurait pu être de nullrouter les IPs attaquantes, mais là aussi les problèmes exposés ci-dessus auraient pû rendre cette méthode plus pénible au final qu'efficace… Par contre ça marche très bien pour dégager le trafic venant d'un sous-réseau spécifique, ce qui n'est pas le cas rencontré ici.

Pour la forme, on peut facilement blackhole/nullrouter avec iproute2:

ip route add blackhole $IP/$NETMASK

HAProxy: time to mess with them.

C'est là où HAProxy intervient. Il permet de faire pas mal de choses tant au niveau de la couche L3 du modèle OSI (couche IP, "réseau"), de la couche L4 dudit modèle (TCP/UDP, couche "transport") que de la couche L7 ("application") du même modèle.

C'est à dire qu'il permet de créer une table temporaire dans laquelle stocker des adresses IPs ainsi que leurs requêtes, et en fonction du contenu de cette table appliquer des règles de contrôle d'accès à chaque IP remplissant une des conditions de ces ACLs. Ça, c'est cool, car ce que je veux exactement faire, c'est stopper la connexion d'une IP précise si elle a effectué un nombre déterminé de requêtes dans un intervalle de temps donné.

Dans le pire des cas (ie, le pire des blogs, celui avec de la neige qui tombe, qui charge 100Mo de gifs de petits chats mignons qui se pètent la tronche, et qui te balance un vieux single horriblement encodé d'un chanteur déjà - et heureusement - oublié), un utilisateur va effectuer ~150 requêtes au grand maximum pour charger la page d'accueil d'un blog et ne rien faire pendant une quinzaine de secondes. En règle générale c'est plutôt 10/20 requêtes par blog. Parfait, "mes" bots fonctionnent sur le principe:

  • Je fais un paquet de requêtes en 20 secondes, toujours le même, 200.
  • Je roupille 10 secondes et je recommence.

OK. Donc je peux créer une table en frontend qui va stocker sur 20 secondes adresses IPs et le nombre de requêtes qu'elles ont effectuées, puis en backend je peux marquer ces IPs avec un flag quelconque qui me servira en frontend pour dropper la connexion à la 199ème requête, pendant 20 secondes via une ACL. Juste ce qu'il faut pour les emmerder, et pour qu'ils ne puissent pas impacter trop fortement ma prod.

Ce qui donne donc, au niveau de ma frontend HAProxy:

    stick-table type ip size 1m expire 20s store gpc0,http_req_rate(20s)
    tcp-request connection track-sc1 src
    tcp-request connection reject if { src_get_gpc0 gt 0 }

Et au niveau de ma backend:

    acl abuse src_http_req_rate(nom_de_ma_frontend) ge 199
    acl flag_abuser src_inc_gpc0(nom_de_ma_frontend)
    tcp-request content reject if abuse flag_abuser

Où bien évidemment "nom_de_ma_frontend" est le nom de votre frontend HAProxy.

Et bim, tout d'un coup le nombre de requêtes entrantes sur la frontend HAProxy redevient normal.

Bizarrement, les bots en question m'ont lâché la grappe le lendemain :)

Euh, mais qu'est ce que ça fait en fait?

  • En frontend, on crée une table qui stocke pendant 20 secondes le nombre de requêtes http / IP

  • en frontend, on traque l'ip source (src) de chaque requête

  • en frontend, si l'IP effectuant une requête possède un score "abuser" supérieur à 0, alors on rejette la connexion (on ne lui sert même pas une 403/404/whatever. Juste "La connexion a été réinitialisée").

  • En backend, si l'IP en question a effectué plus de 199 requêtes, alors on ajoute le flag_abuser à cette IP dans la table, et on rejette la requête (on ne lui sert pas de contenu).

Et voilà, maintenant ils me foutent la paix :)


Tagged under: sysadmin, web, haproxy, linux