==Phrack Inc.== Volume 0x0c, Issue 0x40, Phile #0x0f of 0x11 |=-----------------------------------------------------------------------=| |=-------------=[ Blind TCP/IP hijacking is still alive ]=---------------=| |=-----------------------------------------------------------------------=| |=-----------------------------------------------------------------------=| |=-------------------=[ By lkm ]=-----------------------=| |=-------------------=[ ]=-----------------------=| |=-----------------------------------------------------------------------=| |=------------=[ 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]-- 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