Sicurezza informatica e la vulnerabilità dei software informatici

Sicurezza informatica e la vulnerabilità dei software informatici

Gli applicativi o  software informatici sono  soggetti  a  vulnerabilità (vulnerabilità dei software informatici),  chiamate in gergo informatico Security Bug, generalmente errori commessi nelle fasi che costituiscono il ciclo di vita del software: disegno o progettazione, programmazione, test e  installazione.

Se  un  hacker  individua  un  bug  di  un  applicazione  può  ottenere  privilegi  maggiori a quelli a lui concessi per accedere a file e risorse a cui altrimenti non potrebbe  accedere.  Tale  attacco  viene  chiamato  Privilege  Escalation  e  può essere realizzato in due  modi:

  1. Verticale: l’attacco viene indirizzato verso utenti con privilegi maggiori (amministratori)  dell’aggressore.
  2. Orizzontale: l’attacco viene diretto verso altri utenti con profili analoghi a quello dell’aggressore.

Tali errori costituiscono quindi un’arma molto potente per gli Hacker in quanto permettono di bypassare i sistemi di protezione rendendoli del tutto inutili e consentire lo svolgimento, sul sistema attaccato, di operazioni non autorizzate, suddivisibili in 8 diverse categorie come mostrato nella tabella seguente.

Attualmente sono circa 10.000 le vulnerabilità note per poter aggirare i meccanismi di protezione dei diversi sistemi informatici diffusi.

I tipici errori che vengono commessi in fase di progettazione del software, vengono elencati di seguito.

Fase  1:  Disegno o Progettazione

La prima fase di Disegno e Progettazione prevede la definizione di un thread model, consistente in una descrizione semi-formale di tutti gli eventuali comportamenti scorretti originati dal programma. Questa fase risulta necessaria nel momento in cui il programma viene progettato in quanto esso potrà essere realizzato in modo da far fronte a tali comportamenti ed evitare quindi questi attacchi. Se questa fase non venisse attuata, il software potrebbe contenere delle vulnerabilità.

Per dimostrare l’influenza di scelte errate, commesse in fase di progettazione del software, sulle successive fasi di vita del software viene presentato come esempio un attacco diretto al protocollo ARP: l’ARP Poisoning.

Esempi di incidenti informatici e relative tecniche utilizzate
Esempi di incidenti informatici e relative tecniche utilizzate

Il protocollo ARP viene utilizzato in tutte le reti locali che adottano il proto collo Ethernet. Tale protocollo prevede che tutti gli host della LAN vengano riconosciuti da un indirizzo numerico di 48 bit, noto in gergo informatico come MAC Address. Gli host si scambiano informazioni in rete sotto forma di pacchetti contenenti l’indirizzo del destinatario ovvero il suo mac    address.

L’host che riceve il pacchetto controlla se l’indirizzo presente nel pacchetto è il suo, in caso affermativo elabora le informazioni altrimenti elimina il pacchetto. Generalmente però il mittente non conosce l’indirizzo MAC del destinatario ma solo il suo indirizzo IP o indirizzo simbolico.  è proprio il protocollo ARP che si occupa di individuare il corrispondente MAC Address dato l’indirizzo simbolico del destinatario.

Si supponga ad esempio che il mittente abbia indirizzo [email protected]  e voglia comunicare con il destinatario di indirizzo [email protected], presente sulla  stessa  LAN.

Prima che la comunicazione avvenga, il mittente invia un messaggio a tutti gli host presenti sulla stessa LAN (ARP Request) per conoscere l’indirizzo MAC dell’host a cui deve trasmettere. Gli host che riceveranno il messaggio di ARP Request elimineranno il messaggio se l’indirizzo IP non corrisponde al loro, in  questo  modo  solo  l’host  con  indirizzo  IP  [email protected]  risponderà  al messaggio di ARP Request inviando il proprio MAC Address direttamente al mittente. Per non dover continuamente inoltrare messaggi di ARP Request, il mittente memorizza in una tabella chiamata ARP Cache le corrispondenze tra gli indirizzi IP e quelli MAC finora rintracciate. In questo modo, prima di inviare nuovi messaggi, il mittente verificherà nella propria ARP Cache se l’indirizzo IP a cui deve trasmettere è presente, per ovvie ragioni di efficienza, tuttavia bisogna sottolineare che l’ARP Cache viene memorizzata per un periodo limitato dall’host.

Questo protocollo però possiede delle debolezze in termini di validità in quanto un host può volontariamente informare un altro host che il suo indirizzo MAC è cambiato e questi esegue l’aggiornamento ricevuto senza verificare la validità dell’informazione ricevuta.  Questa debolezza, se da un lato rende il protocollo  più  efficiente  in  termini  di  adattamento  alla  rete,  dall’altro  rende  il protocollo ARP vulnerabile ad attacchi di tipo ARP Poisoning (letteralmente avvelenamento).

Per comprenderne il funzionamento si consideri la rete locale nella figura sottostante.

Esempio di rete locale
Esempio di rete locale

Si supponga che l’utente che opera sull’host con indirizzo IP 10.10.0.6 voglia intercettare il traffico tra i due host di indirizzo rispettivamente 10.10.0.1 e 10.10.0.5. Sfruttando la vulnerabilità del protocollo ARP, per l’attaccante sarà sufficiente inviare un messaggio ARP per l’aggiornamento della cache all’host di indirizzo 10.10.0.1 comunicandogli che l’indirizzo MAC dell’host 10.10.0.5 è stato modificato in 00-56-00-45-67-02. Ricevuto il messaggio di aggiornamento, l’host aggiorna il contenuto della propria cache e ogni volta che dovrà trasmettere dei dati  all’host  di  indirizzo  IP  10.10.0.5  utilizzerà  l’indirizzo  MAC  presente  nella propria tabella 00-56-00-45-67-02 che in realtà appartiene all’host 10.10.0.6. Recentemente si è cercato di ovviare a questi problemi e diverse soluzioni sono state  proposte  dalla  comunità  scientifica,  tuttavia  queste  soluzioni  presuppongono delle patch a livello del kernel del sistema  operativo.

Quindi, per essere adottate universalmente, queste soluzioni devono essere incluse nelle distribuzioni ufficiali dei sistemi operativi. Tuttavia nessun costruttore sembra interessato al problema e le LAN continuano a essere soggette a questo tipo di attacco.

I protocolli vulnerabili, come ARP, sono molti e una volta individuate le vulnerabilità  di  cui  sono  affetti  è  possibile  generare  nuovi  attacchi  che  vengono annientati solo quando vengono individuate le corrispondenti patch correttive.

Fase 2: Programmazione

In fase di programmazione possono essere introdotte vulnerabilità dovute a scelte errate nell’implementazione di alcuni algoritmi oppure dovute all’utilizzo di istruzioni che permettono lo svolgimento di attacchi come il Buffer Overflow. Un esempio di attacco portato a buon fine grazie alle vulnerabilità prodotte in questa fase è offerto dal baco presente nel programma di sistema finger, nell’ambito del sistema operativo SunOS, che consentì nel 1988 la realizzazione del più famoso attacco informatico introdotto da Morris e noto come Internet Worm. Questo attacco sfrutta le vulnerabilità presenti nel linguaggio di programmazione C in cui è scritto gran parte del codice di sistema in ambito Unix e Windows. In particolare si sfrutta la mancanza di controllo del compilatore C riguardo la dimensione della variabile sorgente rispetto a quella di destinazione nel trasferimento di dati. In fase di esecuzione questa mancanza si traduce nel fatto che i  dati superflui della variabile sorgente verranno scritti nelle zone di memoria circostanti la variabile di destinazione.  Per comprendere meglio il funzionamento  si supponga siano definite in linguaggio C le seguenti   variabili:

char Buf1[10]; char Buf2[7];

Il  compilatore  assegnerà  quindi  due  aree  di  memoria  contigue  in  cui  Buf1 otterrà  le  locazioni  di  memoria  con  indirizzi  immediatamente  inferiori  a  quelli assegnati  alle  locazioni  dedicate  a  Buf2  in  quanto  Buf1  A˜ ¨  stata  dichiarata prima, come mostrato nella figura che segue.

Si supponga inoltre che sia presente anche un istruzione che tenta di inserire una stringa di 12 caratteri in Buf1, che ha uno spazio di memoria pari a 10 caratteri.

Buf1 = ‘zzzzzzzzzzzz’;

Il compilatore C non rileva errori di trabocco dei  dati  segnalando  errori come farebbero i compilatori di altri linguaggi quali Java, C++, Pascal; anzi il compilatore C tenta di inserire le variabili in eccesso recuperando spazio dalle variabili contigue a Buf1.

Allocazione in memoria delle variabili
Allocazione in memoria delle variabili Buf1 e Buf2

Di seguito viene descritta in pseudocodice l’operazione di assegnamento della stringa a Buf1:

i = indirizzo iniziale di Buf1; j = 1;

while(l’elemento della stringa di dati da inserire in Buf1 di indirizzo j è diverso dal  carattere di fine stringa)

Buf1[i] = elemento j-esimo della stringa di input i = i +  1;

j = j + 1; endwhile

Alla fine dell’operazione di assegnamento descritta dallo pseudocodice, la situazione in memoria si presenterà come descritto in figura.

Stato della memoria dopo l'operazione di assegnazione
Stato della memoria dopo l’operazione di assegnazione

Questa caratteristica del linguaggio C può essere sfruttata per compiere attacchi a programmi, tuttavia prima di analizzare gli attacchi, vengono di seguito riassunte le modalità con cui viene caricato in memoria un programma per poter essere eseguito.

Un programma eseguibile è composto da due parti:  una parte codice che contiene le istruzioni da eseguire e una parte dati che contiene i dati su cui il codice deve operare. Nel momento in cui si esegue un programma, vengono caricati in memoria centrale le componenti codice e dati relativi alla procedura principale(main). In particolare la parte dati viene caricata in uno stack in cui gli indirizzi decrescono allocando quindi prima le zone dello stack con indirizzi alti come mostrato nello schema nella figura mostrata qui sotto.

Caricamento dati di un programma in esecuzione
Caricamento di una parte di dati, di un programma in esecuzione, all’interno della Memoria Centrale

Ora, se durante l’esecuzione del programma viene richiamata una procedura secondaria, sullo stack viene salvato l’indirizzo di rientro al programma principale, una sorta di segnalibro per memorizzare l’indirizzo da cui riprendere l’esecuzione  del  programma  principale  una  volta  terminata  l’esecuzione della procedura secondaria, seguito dai dati della procedura stessa. Quindi lo Stack diventa come rappresentato nella figura seguente.

Stato dello stack in seguito alla chiamata di una procedura secondaria
Stato dello stack in seguito alla chiamata di una procedura secondaria

In questo modo al termine della procedura secondaria il controllo invece che essere assegnato al programma principale, verrebbe assegnato al programma scritto dall’utente in quanto viene eseguita l’istruzione il cui indirizzo si trova nella zona di memoria riservata all’indirizzo di rientro. Questo programma potrebbe per esempio riuscire a cancellare alcuni file dell’utente dal disco, modificare informazioni o addirittura cancellare l’intero contenuto del disco fisso. L’attacco è, ovviamente, molto difficile da realizzare, e richiede conoscenze molto approfondite delle architetture e dei sistemi operativi corrispondenti.

Ancora oggi il buffer overflow è la tecnica più utilizzata per attaccare i sistemi e nonostante di questa tecnica si conosca ogni dettaglio realizzativo sono ancora molti i programmi di sistema che con questa tecnica sono e possono essere attaccati.

Altro esempio è stato realizzato nel 2001 da un errore di programmazione inseri to nella routine che interpretava le stringhe di input nel programma di Internet Information Server (IIS, web server di Microsoft), il quale consentiva ad un qualunque utente di prendere il controllo del sistema su cui era in esecuzione il programma.

Fase  3: Test

Tuttavia i bug security non sono poi così facili da individuare in quanto difficilmente influenzano il comportamento del programma. Infatti un programma perfettamente aderente alle specifiche per cui è stato concepito può benissimo contenere vulnerabilità rintracciabili solo da accurate fasi di test in quanto i bug security difficilmente provocano messaggi d’errore o visualizzazioni errate. La fase di test deve sottoporre il programma, a partire dal thread model, a tutti i possibili tentativi di attacco e valutarne la reazione. Tuttavia non sempre il testing del codice viene svolto accuratamente in quanto la dimensione dei programmi e la necessità di distribuire a ritmi sempre più sostenuti nuove versioni di software per poter reggere il passo della concorrenza e i costi da sostenere inducono a test approssimativi con inevitabili ricadute sulla sicurezza del prodotto finale.

Fase 4:  Installazione

Infine, in fase di installazione del software informato possono essere introdotti, da utenti poco esperti, errori che permettono agli intrusori di accedere abusivamente al sistema informatico creando problemi alla sicurezza dei dati e delle informazioni (dato sensibile).

Pubblicato da Vito Lavecchia

Lavecchia Vito Ingegnere Informatico (Politecnico di Bari) Email: [email protected] Sito Web: https://vitolavecchia.altervista.org

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *