Le standard WSGI permet de développer
facilement des scripts en
Voici la technique que j'utilise (il y en a plein d'autres, avec Netfilter, ou avec Apache). On crée un
if buckets[ip_client].full():
# Refus
else:
buckets[ip_client].add(1)
En pratique, on va regrouper les adresses IP des clients en préfixes
IP, de manière à limiter le nombre de seaux et à éviter qu'un
attaquant ne disposant d'un préfixe contenant beaucoup de machines ne
puisse éviter la limitation en lançant simplement beaucoup d'adresses
IP différentes à l'attaque. De manière assez arbitraire, on a mis 28
bits pour
ip_client = netaddr.IPAddress(environ['REMOTE_ADDR'])
if ip_client.version == 4:
ip_prefix = netaddr.IPNetwork(environ['REMOTE_ADDR'] + "/28")
elif ip_client.version == 6:
ip_prefix = netaddr.IPNetwork(environ['REMOTE_ADDR'] + "/64")
# Et on continue comme avant :
if buckets[ip_prefix.cidr].full():
...
Et comment se manifeste le refus ? On renvoie le code
status = '429 Too many requests'
output = "%s sent too many requests" % environ['REMOTE_ADDR']
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
Cela donne quoi en pratique ? Testons avec
% for i in $(seq 1 50); do
curl --silent --output /dev/null \
--write-out "$i HTTP status: %{http_code} ; %{size_download} bytes downloaded\n" \
https://www.bortzmeyer.org/apps/counter
done
1 HTTP status: 200 ; 278 bytes downloaded
2 HTTP status: 200 ; 278 bytes downloaded
3 HTTP status: 200 ; 278 bytes downloaded
4 HTTP status: 200 ; 278 bytes downloaded
5 HTTP status: 200 ; 278 bytes downloaded
6 HTTP status: 200 ; 278 bytes downloaded
7 HTTP status: 200 ; 278 bytes downloaded
8 HTTP status: 200 ; 278 bytes downloaded
9 HTTP status: 200 ; 278 bytes downloaded
10 HTTP status: 200 ; 278 bytes downloaded
11 HTTP status: 429 ; 36 bytes downloaded
12 HTTP status: 429 ; 36 bytes downloaded
13 HTTP status: 200 ; 278 bytes downloaded
14 HTTP status: 200 ; 278 bytes downloaded
15 HTTP status: 429 ; 36 bytes downloaded
16 HTTP status: 429 ; 36 bytes downloaded
...
On voit bien l'acceptation initiale, puis le rejet une fois le nombre
maximal de requêtes fait, puis à nouveau l'acceptation lorsque le
temps a passé. Attention en configurant la taille du seau
(
Comme souvent avec les mesures de sécurité, elles peuvent avoir des
effets secondaires, parfois graves. Ici, le serveur alloue une table
indexée par le nombre de préfixes
Si vous voulez les fichiers authentiques et complets, voir les
fichiers de ce blog (fichiers
Merci à David Larlet pour l'amélioration du code.