==Phrack Inc.==
Volume 0x0c, Issue 0x40, Phile #0x0f of 0x11
|=-----------------------------------------------------------------------=|
|=-------------=[ Blind TCP/IP hijacking is still alive ]=---------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------------------------------------------------=|
|=-------------------=[ By lkm ]=-----------------------=|
|=-------------------=[ <lkm_at_phrack_dot_org> ]=-----------------------=|
|=-----------------------------------------------------------------------=|
|=------------=[ Traduit par TboWan pour arsouyes.org ]=-----------------=|
--[ Contents
1 - Introduction
2 - Prérequis
2.1 - Un bref rappel sur TCP
2.2 - L'intéret du IP_ID
2.3 - Liste d'informations à récolter
3 - Description de l'attaque
3.1 - Trouver le port du client
3.2 - Trouver le SND.NEXT du serveur
3.3 - Trouver le SND.NEXT du client.
4 - Discussion
4.1 - Systèmes vulnérables
4.2 - Limitations
5 - Conclusion
6 - Références
--[ 1 - Introduction
S'amuser avec TCP (spoofing en aveugle/hijacking, etc...) était très
populaire il y a quelques années quand les nombres de séquences initiaux
TCP (ISN - Initial Sequence Numbers) étaient devinables (règle 64K,
etc...). Maintenant que ces ISN's sont vraiment plus aléatoires, ces
trucs semblent devenu impossibles.
Dans ce papier, nous allons montrer qu'il est toujours possible
d'effectuer du hijacking TCP en aveugle (sans attaquer le PRNG
[NDT : Pseudo Random Number Generator - générateur de nombres pseudos
aléatoires] responsable de la génération des ISN's, comme dans [1]). Nous
allons présenter une méthode qui fonctionne contre un nombre de systèmes
d'exploitation (Windows 2K, Windows XP, et FreeBSD 4). Cette méthode
n'est pas vraiment intuitive à implémenter, mais est néanmoins entièrement
faisable, puisque nous avons codé un outil qui a été utilisé et fonctionne
contre tous les systèmes vulnérables.
--[ 2 - Prérequis
Dans cette section, nous allons donner les informations nécessaire pour
comprendre ce papier.
----[ 2.1 - Un bref rappel sur TCP
Une connection TCP entre deux hôtes (qui seront appelés respectivements
"client" et "serveur" dans le reste du papier) peut être identifiée par un
tuplet [client-IP, serveur-IP, client-port, serveur-port]. Bien que le
port du serveur soit très connu, celui du client est souvant dans
l'interval 1024-5000, et automatiquement assigné par le système
d'exploitation. (Exemple: la connection d'un mec sur freenode peut être
représenté par [ppp289.someISP.com, irc.freenode.net, 1207, 6667]).
Quand une communication à lieu dans une connection TCP, les en-têtes des
paquets TCP échangés contiennent ces informations (en fait, le paquet IP
contient les IP source/destination, et l'en-tête TCP contient le port
source/destination). Chaque en-tête de paquet TCP contient aussi un champ
de numéro de séquence (SEQ), et un champs d'acquitement (ACK).
Chaqu'un des deux hôtes impliqués dans la connection calcule un nombre
SEQ aléatoire sur 32 bits à l'établissement de la connection. Ce nombre
SEQ initial est appellé l'ISN. Ensuite, chaque fois qu'un hôte envoie un
paquet de N octets de données, il ajoute N au nombre SEQ.
L'émmeteur met son SEQ courant dans le champs SEQ de chaque paquet TCP
sortant. Le champs ACK est remplis avec le prochains nombre SEQ *prévu*
de l'autre hôte. Chaque hôte va garder en mémoire son propre prochain
nombre de séquence (appellé SND.NEXT), et le prochain nombre SEQ prévu
de l'autre hôte (appellé RCV.NEXT).
Clarifions les choses avec un exemple (pour plus de simplicité, nous
considéront que la connection est déjà établie, et les ports ne sont pas
montrés).
[========================================================================]
Client Server
[SND.NEXT=1000] [SND.NEXT=2000]
--[SEQ=1000, ACK=2000, size=20]->
[SND.NEXT=1020] [SND.NEXT=2000]
<-[SEQ=2000, ACK=1020, size=50]--
[SND.NEXT=1020] [SND.NEXT=2050]
--[SEQ=1020, ACK=2050, size=0]->
[========================================================================]
Dans l'exemple précédent, le client commence par envoier 20 octets de
données. Ensuite, le serveur acquite ces données (ACK=1020), et envoie ses
propres 50 octets de données dans le même paquet. Le dernier paquet envoyé
par le client est ce qu'on appelle un "simple ACK". Il acquitte les 50
octets envoyés par le serveur, mais ne contient pas de charge utile. Le
"simple ACK" est utilisé, entre autres, quand un hôte acquitte des
données reçues, mais n'a aucunes données à transmettre. Évidement, tout
paque "simple ACK" bien formé ne sera pas acquité, car ça mènerait à une
boucle infinie. Conceptuellement, chaque octet a un nombre de séquence,
c'est juste que le champ SEQ contenu dans l'en-tête TCP représente le
numéro de séquence du premier octet. Par exemple, les 20 octets du
premier paquet ont les numéros de séquences 1000...1019.
TCP implémente un méchanisme de contrôle de flux en définissant le
concept de "fenêtre". Chaque hôte à une taille de fenêtre TCP (qui est
dynamique, spécifique à chaque connection TCP, et annoncée dans les
paquets TCP), que nous appelleront RCV.WND.
À chaque instant, un hôte va accepter les octets avec un numéro de
séquence entre RCV.NXT et (RCV.NXT+RCV.WND-1). Ce méchanisme garantis
qu'à tout moment, il ne peut pas y avoir plus que RCV.WND octets "en
transit" vers l'hôte.
L'établissement et la fermeture de la connection est gérée par des flags
dans l'en-tête TCP. Les seuls flags utiles dans ce papier sont SYN, ACK,
RST (pour plus d'informations, voir la RFC793 [2]). Les flags SYN et ACK
sont utilisé lors de l'établissement de la connection, comme suit :
[========================================================================]
Client Server
[client picks an ISN]
[SND.NEXT=5000]
--[flags=SYN, SEQ=5000]--> [server choisi un ISN]
[SND.NEXT=5001] [SND.NEXT=9000]
<-[flags=SYN+ACK, SEQ=9000, ACK=5001]--
[SND.NEXT=5001] [SND.NEXT=9001]
--[flags=ACK, SEQ=5001, ACK=9001]-->
...connection établie...
[========================================================================]
Vous noterez que pendant l'établissement, le SND.NEXT de chaque hôte est
incrémenté de 1. C'est pourquoi le flag SYN compte comme un octet
(virtuel), pour le numéro de séquence. Donc, tout paquet avec le
flag SYN va incrémenter le SND.NEXT par 1+paquet_data_size (ici, la
taille de donnée est de 0). Vous noterez aussi que le champs ACK est
optionnel. Le champs ACK ne doit pas être confondu avec le flag ACK, même
s'ils ont un rapport : le ACK flags est mis quand le champs ACK existe.
Le flag ACK est toujours mis sur les paquets impliqué dans
l'établissement d'une connection.
Le flag RST est utilisé pour fermer la connection anormalement (à cause
d'une erreur, par exemple, une tentative de connection sur un port fermé).
----[ 2.2 - L'intérêt du IP_ID
Les en-têtes IP contiennent un flag appellé IP_ID qui est un entier de
16-bits utilisé pour les méchanismes de fragmentation/assemblage de IP.
Ce nombre doit être unique pour chaque paquet IP envoyé par un hôte, mais
sera inchangé par la fragmentation (donc, les fragments d'un même paquet
ont le même IP_ID).
Maintenant, vous devriez vous demander pourquoi le IP_ID est si
intéressant ? Bien, il y a une chouette "fonctionnalité" dans certaines
piles TCP/IP (dont Windows 98, 2K et XP) : ces piles gardent les IP_ID
dans un compteur global, qui est simplement incrémenté avec chaque paquet
IP envoyé. Ceci autorise un attaquant de deviner le compteur IP_ID d'un
hôte (avec un ping par exemple), et donc, savoir quand l'hôte envoie des
paquets.
Exemple:
[========================================================================]
attaquant Hôte
--[PING]->
<-[PING REPLY, IP_ID=1000]--
... attendre un peu, ...
--[PING]->
<-[PING REPLY, IP_ID=1010]--
<attaquant> Oh oh, l'hôte a envoyé 9 paquets IP entre mes deux pings
[========================================================================]
Cette technique est très connue et a dégà été exploitée pour effectuer
des scans de ports vraiment furtifs ([3] et [5]).
----[ 2.3 - Liste d'informations à récolter
Bien, de quoi avons nous besoin pour hijacker une connection TCP
existante ?
D'abord, nous avons besoin de connaître l'IP du client et du serveur,
ainsi que leur port respectifs.
Dans ce papier, nous assumerons que les IP du client et du serveur, ainsi
que le port du serveur sont connus. La difficulté réside dans la
détection du port du client, puisqu'il est aléatoirement assigné par l'OS
du client. Nous verrons dans la section suivante commentle faire, avec le
IP_ID.
La chose suivante dont on a besoin, si nous voulons hijacker dans les
deux sens (envoyer des données du client vers le serveur, et du serveur
vers le client) est de connaître le numéro de séquence du client et celui
du serveur.
Évidement, le plus intéressant est le numéro de séquence du client,
puisqu'il nous permet d'envoyer des données au serveur qui semblent
l'avoir été par le client. Mais, comme le reste du papier vous le
montrera, nous devrons détecter le numéro de séquence du serveur d'abord,
car nous en aurons besoin pour détecter le numéro de séquence du client.
--[ 3 - Description de l'attaque
Dans cette section, nous allons montrer comment déterminer le port du
client, ensuite, le numéro de séquence du serveur, et enfin le numéro de
séquence du client. Nous considèreront que l'OS du client est un OS
vulnérable. Le serveur peut fonctionner sur n'importe quel OS.
----[ 3.1 - Trouver le port du client
En admettant que nous connaissions déjà l'IP du client et du serveur, et
le port du serveur, il y a des méthodes bien connues pour tester si un
port est le bon. Pour le faire,on peut envoyer un paquet TCP avec le
flag SYN vers server-IP:server-port, à partir du client-IP:guessed-port
(nous devons pouvoir envoyer des paquets spoofés pour que cette
technique fonctionne).
Voici ce qu'il se passera quand nous enverrons notre paquet si le port du
client qu'on teste N'EST PAS le bon :
[========================================================================]
Attaquant (se fait passer pour le client) Server
--[flags=SYN, SEQ=1000]->
réel client
<-[flags=SYN+ACK, SEQ=2000, ACK=1001]--
... le client n'a pas initié cette connection, il l'annule en envoyant un
RST ...
--[flags=RST]->
[========================================================================]
Voici ce qu'il se passera quand nous enverrons notre paquet si le port du
client qu'on teste EST le bon :
[========================================================================]
Attaquant (se fait passer pour le client) Server
--[flags=SYN, SEQ=1000]->
Client réel
... à la réception de notre SYN, le serveur répond d'un simple ACK...
<-[flags=ACK, SEQ=xxxx, ACK=yyyy]--
... le client ne répond rien à un "simple ACK"
[========================================================================]
Maintenant, ce qui est important dans tout ceci est que dans le premier
cas, le client envoie un paquet, et pas dans le second. Si vous avez lu
attentivement la section 2.2, vous savez que cette particularité peut
être détectée en testant le compteur IP_ID du client.
Donc, voici tout ce que nous devons faire pour tester si un port du
client est le bon :
- envoyer un PING au client, noter le IP_ID
- envoyer notre SYN spoofé
- renvoyer un ping au client, noter le nouveau IP_ID
- comparer les deux IP_ID et déterminer si le port était le bon.
Évidement, si quelqu'un veut faire un scanner efficace, il y a beaucoup
de difficultés, notement le fait que le client peut transmettre des
paquets de son propre chef entre nos deux pings, et la latence entre le
client et le serveur (qui affecte le délay après lequel le client enverra
son paquet RST dans le cas d'un mauvais port testé). Coder un scanner de
port-client efficace est laissé en exercice au lecteur :). Avec notre
outil - qui mesure la latence avant l'attaque et tente de s'adapter au
traffic du client en temps réel - le port du client est d'habitude trouvé
en moins de 3 minutes.
----[ 3.2 - Trouver le SND.NEXT du serveur
Maintenant que nous avons (heureusement:)) le port du client, nous devons
connaître le SND.NEXT du serveur (en d'autre mot, son numéro de séquence
courant).
Chaque fois qu'un hôte recoit un paquet TCP avec les bons ports
source/destination, mais un seq et/ou ack incorrecte, il renvoie
simplement un simple ACK avec les bons nombre SEQ/ACK. Avant que nous
n'étudions ce cas, définissons exactement ce qu'on entent par une
combinaison correcte de seq/ack, comme définie dans la RFC793 [2] :
Un SEQ correcte est un SEQ qui est entre RCV.NEXT et (RCV.NEXT+RCV.WND-1)
de l'hôte qui recoit le paquet (quelques douzaines de kilooctets au moins).
Un ACK correcte est un ACK qui correspond à un numéro de séquence de
quelque chose que l'hôte recevant le ACK a déjà envoyé. C'est à dire, le
champs ACK du paquet recu par un hôte doit être plus petit ou égal à son
propre SND.SEQ courant, sinon, le ACK est invalide (vous ne pouvez pas
acquitter des données qui n'ont jamais été envoyées).
Il est important de noter que l'espace des numéros de séquence est
"circulaire". Par exemple, la condition utilisée par l'hôte recevant pour
vérifier la validité du ACK n'est pas simplement la comparaison "ACK <=
SND.NEXT du receveur", mais la comparaison signée "(ACK - receiver's
SND.NEXT) <= 0".
Maintenant, retournons à notre problème original : nous voulons
deviner le SND.NEXT du serveur. Nous savons que si nous envoyons un
mauvais SEQ ou ACK du client vers serveur, le client va renvoyer
un ACK, alors que si nous trouvons le bon, le client ne renverra rien.
Comme pour la détection du port du client, ceci sera testé avec le IP_ID.
Si nous regardons à la formule de vérification du ACK, nous notons que si
nous prenons deux valeurs aléatoire de ACK, soit ack1 et ack2, tel que
|ack1-ack2| = 2^31, alors, exactement l'une d'entre elle est vaile. Par
exemple, soit ack1=0 et ack2=2^31. Si le réel Ack est entre 1 et 2^31,
alors ack2 sera accepté. Si le réel ACK est 0 ou entre (2^32 - 1) et
(2^31 + 1), alors, c'est le ack1 qui est acceptable.
En prennant ceci en considération, on peut plus facilement scanner
l'espace de numéros de séquences pour trouver le SND.NEXT du serveur.
Chaque test impliquera d'envoyer deux paquets, chaqu'un avec le champ SEQ
contennant le SND.NEXT qu'on teste. Le premier paquet (resp. le deuxième)
aura son champ ACK à ack1 (resp. ack2), pour être sur que si le SND.NEXT
est correcte, au moins un des deux paquets soit accepté.
L'espace de numéro de séquences et plus grand que l'espace des ports du
client, mais deux fait rendent ce scan plus facile :
D'abord, quand le client reçoit notre paquet, il répond immédiatement. Il
n'y a pas de problème de latences entre le client et le serveur comme
dans le scan du port du client. Donc, le temps entre deux tests du IP_ID
est très court, accélérant notre scan et réduisant grandement les chances
que le client ait du traffic entre nos test et ennuie notre détection.
Ensuite, il n'est pas nécessaire de tester tous les numéros de séquence
possibles, grace à la fenêtre du récepteur. En fait, nous n'avons besoin
que de l'approximer. (2^32 / RCV.WND du client) tests au pire (ce fait a
déjà été montré dans [6]). Bien sûr, nous ne connaissons pas le RCV.WND
du client. On peut prendre un test réel avec RCV.WND=64K, pour effectuer
le scan (essayer tous les SEQ multiples de 64K). Alors, si nous n'avons
rien trouvé, on peut essayer tous les seq = seq = 32K + i*64K pour tout
i. Ensuite, tous les SEQ du type seq=16k + i*32k, et ainsi de suite...
rétrecissant la fenêtre, en évitant de tester deux fois les mêmes SEQ's.
Sur une connection typique "moderne", ce scan prend d'habitude moins de
15 minutes avec notre outil.
Une fois que le SND.NEXT du serveur connu, et une méthode pour passer
outre notre ignorance du ACK, nous pouvons hijacker la connection dans le
sens "server -> client". Ce n'est pas si mal, mais pas très utile, nous
préfèrerions être capable d'envoyer des données du client vers le
serveur, pour que le client exécute une commande, etc...
Pour y arriver, nous devons découvrir le SND.NEXT du client.
----[ 3.3 - Trouver le SND.NEXT du client.
Que pouvons-nous faire pour trouver le SND.NEXT du client ? Évidement,
nous ne pouvons pas utiliser la même méthode que pour le SND.NEXT du
serveur, parce que l'OS du serveur est probablement pas vulnérable à
cette attaque et ensuite, l'énorme traffic réseau sur le serveur rendra
l'analyse de l'ID_IP infaisable.
Cependant, nous connaissons le SND.NEXT du serveur. Nous savons aussi que
le client SND.NEXT est utilisé pour vérifier le champt ACK des paquets
arrivant chez lui. Donc, nous pouvons envoyer des paquets du serveur vers
le client avec le champs SEQ mis à la valeur du SND.NEXT du serveur,
prendre un ACK, et déterminer (encore une fois avec l'IP_ID) si ce ACK
est acceptable.
Si on détecte que notre ACK est acceptable, ça veut dire que (guessed_ACK
- SND.NEXT) <= 0. Sinon, ça veut dire... bref, vous avez deviné,
(guessed_ACK - SND_NEXT) > 0.
En utilisant cette information, on peut trouver le SND_NEXT exacte en au
plus 32 essais en faisant une recherche dichotomique (légèrement modifiée
parce que l'espace des numéros est circulaire).
Maintenant, enfin, nous avons toutes les informations requises et nous
pouvons effectuer l'hijacking de session depuis à la fois le client et le
serveur.
--[ 4 - Discussion
Dans cette section, nous allons essayer d'identifier les systèmes
affectés, discuter des limitations de cette attaque, et présenter des
attaques similaires sur des systèmes plus vieux.
----[ 4.1 - Systèmes vulnérables
Cette attaque a été testée sur Windows 2K, Windows XP <= SP2 et FreeBSD
4. On doit noter que FreeBSD a une option du noyau pour randomiser
l'IP_ID, qui rend cette attaque impossible. Autant que nous le sachions,
il n'y a pas de correction pour Windows 2K et XP.
Le seul "bug" qui rend cette attaque possible sur les systèmes
vulnérables est la non-randomisation des IP_ID. Les autres comportements
(la vérification du ACK qui nous permet de faire une recherche
dichotomique, etc...) sont prévues par la RFC793[2] (cependant, il y a eu
des travaux pour réduire ces problèmes dans [4]).
Il est intéressant de voir que, autant qu'on ait pu le tester, seul
Windows 2K, Windows XP et FreeBSD sont vulnérables. Il y a d'autres
systèmes qui utilisent le même système d'incrémentation de l'IP_ID, mais
ils n'utilisent pas le même méchanisme de vérification du ACK. Hummm,
cette similarité entre les comportements des piles TCP/IP de Windows et
FreeBSD est troublante... :) MacOS X est basé sur FreeBSD mais n'est pas
vulnérable car il utilise un autre schéma de numérotation des IP_ID.
Windows Vista n'a pas été testé.
----[ 4.2 - Limitations
L'attaque décrite a des limitations variées :
D'abord, l'attaque ne fonctionne pas "tel quelle" sous Windows 98. Ce n'est
pas vraiment une limitation, car le SEQ initial sous Windiws 98 est égal
à l'uptime de la machine en milisecondes, modulo 2^32. Nous ne
discuteront pas du comment hijacker avec Windows 98 car c'est une "trivial
joke" :)
Ensuite, l'attaque sera difficile si le client a une connection lente, ou
a beaucoup de traffic (ennuyant pour les analyses du IP_ID). Il y a aussi
le problème de la latence entre le client et le serveur. Ces problèment
peuvent être réduits en écrivant un outil intellient qui mesure la
latence, détecte quand l'hôte a du traffic, etc ...
Enfin, nous avons besoin d'un accès vers l'hôte. Nous devons pouvoir
envoyer des paquets et recevoir ses réponses pour récupérer l'IP_ID.
N'importe quel type de paquet est bon, ICMP, ou TCP, ou autre. L'attaque
ne sera pas possible si l'hôte est derrière un firewal/NAT/... qui bloque
absolument tout type de paquet, mais un seul port non filtré (même s'il
est fermé sur le client) suffit pour rendre l'attaque possible. Le
problème se présente avec Windows XP SP2 et suivant, qui viennent avec un
firewall intégré. Windows XP SP2 est vulnérable, mais son firewall peut
empecher l'attaque dans certaines situations.
--[ 5 - Conclusion
Dans ce papier, nous avons présenté une méthode d'hijacking TCP en
aveugle qui fonctionne sous Windows 2K/XP, et FreeBSD 4. Bien que cette
méthode ait quelques limitations, elle est parfaitement faisable et
fonctionne contre un grand nombre d'hôtes. En plus, un grand nombre de
protocoles au dessus de TCP utilisent toujours des communications non
chiffrées, l'impacte sur la sécurité du hijacking TCP en aveugle n'est
donc pas négligeable.
--[ 6 - Références
[1] http://lcamtuf.coredump.cx/newtcp/
[2] http://www.ietf.org/rfc/rfc793.txt
[3] http://insecure.org/nmap/idlescan.html
[4] http://www.ietf.org/internet-drafts/draft-ietf-tcpm-tcpsecure-07.txt
[5] http://seclists.org/bugtraq/1998/Dec/0079.html
[6] http://osvdb.org/reference/SlippingInTheWindow_v1.0.doc