29 febbraio 2008

Note sulla crittografia nei database: i data-at-rest

Dopo avere visto gli approcci alla cifratura dei dati in transito, vediamo ora quelli rivolti ai dati memorizzati (e archiviati). La cifratura dei dati su disco è da preferire quando si conservano dati che non dovrebbero essere visibili se non ai legittimi proprietari. È il caso dei numeri delle carte di credito, a cui non dovrebbero accedere neppure i DBA; ma va contemplata anche l’eventualità di una copia illegale dei file del database, o del furto del server o dei dischi su cui risiedono i dati.

Le strategie seguite sono tipicamente tre:

  • Cifratura a livello applicazione: tutta la gestione è demandata ad apposite librerie crittografiche del linguaggio di programmazione usato nello sviluppo dell’applicazione per la gestione della base dati (come le Java Cryptographic Extensions). Questo approccio è completamente trasparente al database. Se questa soluzione è utile nei casi in cui le norme di sicurezza sono molto stringenti (è impossibile usare i dati all’infuori dell’applicazione che li ha cifrati), è però poco pratico perché limita l’utilizzo di stored procedure e appesantisce lo sviluppo dell’applicazione.

  • Cifratura a livello del file system: sfrutta le capacità dei sistemi operativi più evoluti di gestire i file in forma cifrata. Anche in questo caso, l’operazione è trasparente al database. Ha un grosso limite nel decadimento delle prestazioni, perché ogni accesso ai file deve passare attraverso la fase di cifratura/decifratura.

  • Cifratura a livello del database: si usano le funzionalità offerte dal DBMS o da estensioni sviluppate da terzi. Risulta essere la soluzione più pratica, e infatti è la più utilizzata. Tuttavia “pratica” non va intesa come semplice: a questo approccio è legata la gestione di un insieme di chiavi che non è affatto banale. Tipicamente, a ogni attributo che si intende cifrare è associata una chiave simmetrica. Gli utenti del database sono forniti di una chiave pubblica e di una privata personali. Quando a uno di questi utenti viene assegnato il permesso di accedere a un attributo cifrato, la relativa chiave simmetrica viene cifrata con la chiave pubblica dell’utente e posizionata in una locazione accessibile. Ad essa l’utente potrà accedere mediante la sua chiave privata, che gli consentirà di operare sull’attributo.

Comunque, quale che sia la strategia adottata, la prima considerazione da fare riguarda quali dati nascondere. Gli algoritmi crittografici consumano risorse sul server, con il conseguente decadimento delle prestazioni. Per questo motivo è necessario individuare quali attributi siano assolutamente da mascherare, evitando dove possibile gli attributi usati come chiavi o associati a indici, perché genererebbero un sovraccarico dovuto alla decifrazione ad ogni scansione della tabella a cui appartengono.

Un’altra questione riguarda le funzionalità di
backup e ripristino. I backup vanno cifrati al pari dei dati in linea, altrimenti all’aggressore basterà procurarsi i file di backup per aggirare il problema (operazione anche meno rischiosa se non è previsto un auditing sugli accessi ai dati archiviati). Inoltre, bisogna tenere presente che, se la politica di sicurezza aziendale prevede il cambiamento periodico delle chiavi, bisognerà gestire e conservare (in maniera sicura) quelle usate per generare i backup.

Vista la complessità e la difficoltà implementativa di queste scelte, è indispensabile documentarsi approfonditamente prima di adottarle per i propri sistemi, studiando in particolare le funzionalità messe a disposizione dai singoli DBMS. Esse tuttavia - al momento - non affrontano ancora in modo integrato questioni complesse come per esempio la gestione della cifratura in presenza di clustering, di repliche del database, dell’auditing. In questo settore specifico si potrà scegliere di usare prodotti di terze parti, che generalmente offrono soluzioni più complete e più semplici da utilizzare.

26 febbraio 2008

Note sulla crittografia nei database: i dati in transito

Non sono sparito... In questo periodo sto trascurando il blog perché sono assorbito da un progetto decisamente interessante, di cui forse parlerò in futuro. Nel frattempo, pubblico qualche nota su un argomento centrale per la sicurezza: la cifratura dei dati.

Nonostante tutte le precauzioni messe in atto per proteggere i sistemi informatici e i dati custoditi, è sempre possibile che un aggressore riesca a penetrare le difese e raggiungere il cuore del sistema, la base dati. Quando l’attacco ha successo, l’estrema difesa è rappresentata dall’uso della
crittografia, che rende illeggibili i dati a chi non possiede le chiavi per decodificarli.

Un ottimo libro sulla crittografia per chi volesse approfondire l'argomento è Practical Cryptography di Niels Ferguson and Bruce Schneier, che in qualche modo segue il precedente Applied Cryptography, ancora di Schneier.

Nel settore delle basi dati si sono affermati due approcci alle tecniche crittografiche, nati da esigenze diverse: la
cifratura dei dati in transito e la cifratura dei dati memorizzati (o data-at-rest). Nel primo caso, che vedremo oggi, si intende proteggere le comunicazioni, perché la minaccia di furto dei dati si manifesta a livello del canale; nel secondo caso si vuole proteggere (almeno) una parte dei dati perché particolarmente riservata - come nel classico esempio dei numeri di carte di credito - dall’accesso sia di persone esterne che degli utenti interni del database.

Cifratura dei dati in transito

Tutti i DBMS all’installazione aprono delle porte di default per le loro comunicazioni, che gli amministratori meno accorti tendono a non cambiare. Gli hacker conoscono queste porte, e quasi sempre sono esperti di reti. Riescono a intercettare facilmente le trasmissioni, che generalmente avvengono in chiaro (o quasi). Se il canale di comunicazione è considerato a rischio, è opportuno cifrare/decifrare la trasmissione ai suoi estremi, ovvero all’ingresso e all’uscita del canale - avendo tuttavia ben presente che la cifratura delle trasmissioni ha un impatto considerevole sulle prestazioni. Se si sceglie questo approccio, ovvero si considera prioritaria la minaccia sul canale, i dati contenuti nelle tabelle possono rimanere non criptati (ma nulla vieta di ricorrere a entrambe le soluzioni).

Per intercettare le comunicazioni, un attaccante deve avere accesso alla rete e sapere interpretare il flusso delle comunicazioni. L’accesso alla rete è certamente semplificato se si usa una macchina facente parte della rete-bersaglio, o se si ha accesso fisico a uno dei suoi switch (magari attraverso una porta SPAN *): è sufficiente configurare la scheda di rete in modalità promiscua per intercettare tutte le trasmissioni che avvengono all’interno della rete. Per interpretare il flusso delle comunicazioni, oltre ad avere una minima conoscenza dei protocolli di rete - in pratica del TCP/IP - e dei protocolli dei DBMS che si appoggiano su di essi, basta l’uso di uno dei tanti strumenti disponibili in internet come
Tcpdump o Ethereal per intercettare i pacchetti e acquisirne i payload, al cui interno sono veicolati i payload del database. Usando questa tecnica, l’attaccante sarà in grado di intercettare il processo di autenticazione (il login al database), i comandi SQL (dai quali può estrapolare la struttura della base dati), e i dati restituiti dalle query.

[* Switch Port Analyzer. Molti switch evoluti sono dotati di una porta SPAN a scopi amministrativi, sulla quale (e solo su di essa) viene trasmessa copia di tutti i dati inviati alle altre.]

Per proteggere la trasmissione si possono adottare approcci diversi. Nello specifico:

  • Funzionalità dedicate interne al database: alcuni DMBS offrono dei package per il supporto e la gestione della cifratura delle trasmissioni, come nel caso del pacchetto Oracle Advanced Security incluso nella versione Enterprise di Oracle (può essere utile consultare a questo scopo il volume Oracle security handbook di Theriault, Newman e Vandivier). Quando un client tenta di aprire una connessione col server, il listener di Oracle avvia una sequenza di negoziazione sul sistema di cifratura, mediante la quale comunica al server i metodi crittografici che supporta. Il server li confronta con quelli che ha a sua disposizione, e se trova delle corrispondenze attiva quello a cui è data la priorità nei suoi file di configurazione; in caso contrario, rifiuta la connessione. Poiché solitamente questo genere di soluzioni è costoso (per l’acquisto delle versioni più care o di pacchetti aggiuntivi), la loro diffusione è limitata rispetto alle altre.

  • Connessioni speciali: in sostanza si riducono tutte all’uso di SSL (Secure Socket Layer), che è adottato praticamente da tutti i DBMS sul mercato. Il protocollo SSL utilizza metodi di cifratura a chiave pubblica e richiede l’uso di certificati a chiave pubblica per verificare l’identità del DB server e della macchina su cui è in esecuzione l’applicazione.

  • Tunneling: in questo caso si sfrutta il protocollo SSH, che è diventato uno standard di fatto per l’amministrazione remota di sistemi Unix e di dispositivi di rete, in sostituzione del protocollo Telnet, privo di protezione contro le intercettazioni. Questa tecnica è implementata in modo trasparente rispetto al database, perché i dati sono cifrati direttamente nel tunnel. Per cifrare il traffico del database si stabilisce un tunnel SSH mediante il port forwarding: si specifica una porta A sul client che fungerà da accesso al tunnel, il quale consegnerà i pacchetti sulla porta specificata B del server.

    Esempio: ssh -L 9999:localhost:4304 192.168.0.1

    Con questo comando si genera un tunnel tra la porta 9999 del client (localhost) e la porta 4304 del server (192.168.0.1). Ogni trasmissione che abbia luogo attraverso la porta A del client passerà automaticamente attraverso il tunnel SSH, cifrata, fino alla porta B sul server.

  • Demandare al sistema operativo: anche in questo caso, come nel precedente, la cifratura avviene in modo trasparente per il database, ma l’operazione è direttamente a carico del sistema operativo. Si usa lo standard IPSec, che a sua volta crea una sorta di tunnel cifrato, che però ha effetto su tutto lo stack TCP/IP. È fondamentale che il servizio di cifratura di IPSec sia attivo su tutte le macchine che devono essere coinvolte.


Queste le soluzioni più comuni per la cifratura dei dati in transito. Nel prossimo post completeremo la panoramica vedendo quelle per i dati memorizzati. Stay tuned!

8 febbraio 2008

Analisi forense su Oracle - analisi esadecimale dei Redo Logs

Le due tecniche di analisi forense su Oracle viste nei post precedenti sono semplici e relativamente rapide, ma solo l’analisi esadecimale dei Redo Logs restituisce una descrizione completa di quello che è accaduto nella base dati. Per questa tecnica, in realtà complessa e poco documentata*, è sufficiente usare uno dei tanti editor esadecimali disponibili in rete, da usare come sempre su copie dei log esatte e certificate (per garantirne la validità di prova in sede giudiziaria).

* La Oracle non fornisce ufficialmente le specifiche dei formati interni dei file. Una delle poche fonti di informazioni è il sito www.databasesecurity.com, da cui è stato tratto l’esempio riportato in questo paragrafo (si veda in particolare: D. Litchfield, “Dissecting the Redo Logs”, white paper della serie Oracle Forensics), integrato nelle parti che mi sembravano meno chiare.

La maggior parte dei file binari di dati e di log in Oracle condivide la stessa struttura. In testa è presente un header, la cui prima parte riporta la tipologia di file (valore 0x22 per i Redo Logs), la dimensione del blocco logico (solitamente 512 byte), e il numero di blocchi che compongono il file (a cui va aggiunto quello occupato dall’header, non contato). Moltiplicando il totale dei blocchi per la dimensione del singolo blocco si ottiene la dimensione effettiva del file. Segue quindi un magic number, usato dal DBMS per determinare se effettivamente si tratta di un file Oracle.La seconda parte dell’header contiene una serie di informazioni relative all’istanza del database, tra cui il suo SID (Serial Identificator), la versione del database, la data e l’ora in cui è partita l’operazione di log. Inoltre contiene quattro coppie di numeri a 32 bit: Low, Next, Enabled e Thread Closed System Change Numbers (SNC), ognuno associato a un timestamp (data e ora).

Gli SNC sono una sorta di “segnalibri” per indicare un punto in cui lo stato del database è cambiato, per esempio per effetto di una INSERT seguita da un COMMIT.
I primi due elencati, in particolare, indicano rispettivamente la prima e l’ultima transazione registrata nel log. In caso di necessità si potrà quindi ripristinare il database a una particolare transazione mediante il relativo SCN.

Riguardo ai blocchi, ognuno di essi ha un header di 16 byte che inizia con un identificativo, seguito dal numero di blocco, dal numero di sequenza e da un offset.



È da notare che un record del Redo Log può eccedere le dimensioni di un blocco, continuando nel successivo. L’investigatore dovrà tenerne opportunamente conto, facendo attenzione alla cadenza degli identificatori di inizio blocco. L’offset rappresenta il Relative Block Address (indirizzo relativo di blocco), ovvero il punto all’interno del blocco in cui inizia un nuovo record del log. Chiude l’header il valore di checksum del blocco, che garantisce l’integrità dei dati in esso riportati.

Sui checksum in particolare si concentra l’attenzione degli hacker più esperti, che cercheranno di riscrivere in breve tempo i record dei Redo Logs (mediante appositi programmi) lasciando intatto tutto il resto. In questi casi il lavoro dell’investigatore si complica in maniera esponenziale perché i checksum risulteranno falsamente corretti; ma fortunatamente sono pochissime le persone in grado di compiere operazioni del genere in un tempo limitato. È qui utile sottolineare quanto detto in altri post: una strategia di difesa in profondità complica il lavoro dell’aggressore, ed è più probabile che a qualche livello rimanga traccia del suo passaggio, nonostante i suoi tentativi - per quanto sofisticati - di eliminare le prove.


Continuiamo il quadro sulla struttura di un Redo Log parlando di come vengono gestiti i timestamp. Questi numeri a 32 bit rappresentano il numero di secondi trascorsi dalla mezzanotte del primo gennaio 1988 (rappresentata * come 01/01/1988 00:00:00).

* In realtà l’algoritmo di Oracle che produce il valore non calcola il numero esatto di secondi trascorsi, ma ai fini di questa trattazione potremo accettare questa semplificazione. Il lettore interessato ad approfondire potrà consultare il paper di Litchfield già segnalato.


Infine, descriviamo un Redo Record: per ogni SCN, esiste nel log un record che ne descrive tutte le modifiche apportate al database. Ogni record inizia con un header, seguito da uno o anche più change vector. Ad esempio, nel caso di una INSERT su una tabella con un indice verranno generati diversi change vector: uno per il redo e uno per l’undo della INSERT, un insert leaf row per l’indice e un commit.

Ai change vector è associato un codice operativo, formato da un codice di livello e da un sotto-codice operazionale separati da un punto. Esistono più di 150 codici operativi, di cui si elencano i più comuni:



A questo punto abbiamo gli elementi principali necessari per decodificare un Redo Log. I passi da seguire sono i seguenti:

  • messa in sicurezza del server violato (secondo le metodologie forensi fondamentali per garantire l’autenticità delle prove raccolte);
  • copia dei Redo Logs su cui effettuare tutte le analisi;
  • di ogni oggetto del database inventariare ID, tipo e proprietari.
Esamineremo a titolo d’esempio un unico record di uno dei file di log, la cui codifica esadecimale, riportata nella figura seguente, comincia dall’header del relativo blocco.



L’offset dell’header (0x10) indica che il record comincia all’indirizzo 0x1d2810 (0x1d2800 + 0x10). Qui troviamo la dimensione in byte del record: 0x01A8 (424 byte).

A 68 byte dall’inizio del record troviamo la coppia 0B 02, codice operativo dell’operazione: in decimale “11 02”, corrispondente a un inserimento su riga singola. Nei quattro byte preced
enti è riportato il timestamp dell’inserimento (0x24CC67F9), che è possibile convertire in forma leggibile usando all’inverso l’algoritmo di Oracle (13:15:37 del 16 marzo 2007).

A 22 byte dal codice operativo è riportato il codice identificativo dell’oggetto del database coinvolto dall’operazione. In questo caso 0x0057, ovvero 87 in decimale. Dall’inventario degli oggetti si evince che l’ID 87 corrisponde alla tabella sysauth$ (che tiene traccia dei ruoli e dei membri ad essi associati) di proprietà dell’utente sys.

La coppia di byte successiva serve per dedurre il numero di colonne coinvolte e va interpretato come dimensione in byte di un vettore. Il valore rappresentato è 0x000C, in decimale 12. Ogni elemento del vettore occupa 2 byte, da cui 6 elementi. Il primo è occupato dal valore di dimensione, il secondo e il terzo sono le coppie successive (0x0014 e 0x0031), che sono dei valori sostanzialmente fissi, quindi seguono le ultime tre coppie, che rappresentano le tre colonne su cui ha agito l’operazione che stiamo esaminando. Queste tre coppie riportano il numero di byte inseriti nelle colonne: 2 nella prima e nella seconda, 3 nella terza.

Le prime tre colonne della tabella sysauth$ sono di tipo numerico: la posizione dei valori inseriti è indicata dal byte immediatamente successivo, che può valere 01 (valori a 72 byte di distanza), 02 (valori a 64 byte di distanza) o 17 (valori a 120 byte di distanza). Nel nostro caso il valore è 01, per cui individuiamo il primo dato (0xC102) a 72 byte di distanza.


I valori numerici sono gestiti da Oracle in modo particolare. I numeri decimali vengono separati in coppie (per esempio 12345 in 01-23-45) e l’ordine di grandezza è codificato su un byte: C1 per i numeri compresi tra 1 e 99, C2 per numeri tra 100 e 9.999, C3 per numeri tra 10.000 e 1.000.000 e così via. Ad ogni coppia, se diversa da zero, viene associato un byte e al numero da rappresentare - in esadecimale - è aggiunto 1. A titolo di esempio:



Come si diceva, il primo dato inserito è 0xC102, seguito da 0xC105 e 0xC20931, ovvero i valori 1, 4 e 848. Allora possiamo ricostruire l’operazione compiuta alle 13:15:37 del 16 marzo 2007:

INSERT INTO SYS.SYSAUTH$ (GRANTEE#,PRIVILEGE#, SEQUENCE#) VALUES (1,4,848);

Dall’inventario compiuto a inizio indagine si evince che l’utente “1” corrisponde a PUBLIC e che il privilegio “4” è quello di DBA. In altre parole, questa operazione ha reso l’utente PUBLIC (ovvero chiunque!) un amministratore del database.

Nel log, ogni record riporta subito dopo l’operazione anche la relativa UNDO, nel quale è segnalato anche l’ID dell’utente che ha impartito il comando. Nel caso della INSERT in esame il valore è 0x36 (54 decimale): consultando la mappatura degli ID si potrà capire se l’operazione è stata effettuata dall’utente sys, come sarebbe lecito aspettarsi (l’accesso alla tabella sysauth$ gli è riservato), o se non si tratti piuttosto di un utente a cui è stato illecitamente consentito l’accesso. In tal caso, individuato l’utente con il profilo compromesso, si dovrà procedere a investigare sulle operazioni che sono state da lui compiute e su quelle che hanno portato all’assegnazione di privilegi non previsti. In questo modo si riuscirà a riscostruire una linea temporale delle operazioni illecite compiute sul database, da presentare in sede giudiziaria.

L’esempio trattato, apparentemente complicato, è in realtà uno dei casi più semplici tra quelli affrontati nell’analisi forense delle basi dati, e rappresenta una delle operazioni preliminari dell’indagine. Sul sito segnalato sono presenti anche gli altri white paper di Litchfield sull'analisi forense su Oracle: la lettura è molto interessante, e la consiglio per chi è interessato a questi argomenti.

4 febbraio 2008

Analisi forense su Oracle - LogMiner

Continuiamo la nostra carrellata sulle tecniche di analisi forense su un database Oracle. Dopo avere fatto conoscenza con l'elemento fondamentale per l'indagine, il Redo Log, e aver visto una tecnica di base, il dump del log, andiamo a incontrare un utile strumento fornito da Oracle stesso.

LogMiner è un'applicazione Oracle (edizione Enterprise) che consente di ispezionare direttamente i Redo Logs usando interrogazioni SQL. È scritto quasi tutto nel linguaggio procedurale PL/SQL di Oracle, e le sue funzionalità sono utilizzabili mediante l'interfaccia a linea di comando, oppure attraverso l’interfaccia grafica Oracle LogMiner Viewer (vedi figura), parte dell'Oracle Enterprise Manager, applicazione java per la gestione dei database Oracle.




LogMiner è stato pensato come strumento di ripristino in caso di problemi sul database. Per un'esauriente trattazione di LogMiner in quest'ambito si veda Expert One-on-One Oracle di Thomas Kyte.

Il principio di funzionamento è lo stesso della tecnica di dump che abbiamo visto nel post precedente, consentendo però l’accesso diretto ai file binari. LogMiner funziona all’interno di un’istanza del database, ma anche in questo caso può lavorare sui log di un'istanza diversa, soluzione da preferire sempre durante la fase investigativa.

L'uso di LogMiner in ambito forense è apertamente suggerito da Pete Finnigan, uno dei massimi esperti a livello mondiale di sicurezza su sistemi Oracle (autore dell'ottimo Oracle security step-by-step). Tuttavia recentemente un altro esperto del settore, Paul Wright, pur concordando sull'utilità come strumento di analisi forense, ha individuato un comportamento anomalo riguardo alla gestione delle date, che potrebbe inficiare i risultati delle analisi condotte con LogMiner su database a elevato numero di transazioni (i dettagli sono descritti in “Oracle database forensics using LogMiner”, intervento alla SANS Security 2004 Conference, Londra, giugno 2004).

Secondo Wright, quando LogMiner tratta e recupera dai log dati di tipo TIMESTAMP (mese, giorno, anno, ore, minuti, secondi, frazioni di secondo fino a 6 cifre decimali), in realtà li gestisce come dati di tipo DATE (mese, giorno, anno, ore, minuti, secondi), perdendo quindi le frazioni di secondo.

Lo stesso problema falsa le operazioni di ripristino dei dati di tipo TIMESTAMP, poiché vengono caricati con la parte decimale sempre pari a .000000, qualunque sia il reale valore memorizzato nei Redo Logs. A questo difetto sono soggette le versioni 9i e successive di Oracle, perché il formato TIMESTAMP è stato introdotto proprio con la 9i, e apparentemente nell'aggiornamento di LogMiner (nato con la versione 8) si è trascurata la differenziazione sul formato DATE.

Normalmente si tratta di un problema di poco conto per gli utilizzatori del database, ma i risultati ottenuti da LogMiner a fini investigativi potrebbero essere contestati in sede giudiziaria per l’intrinseca mancanza di precisione, almeno fino a quando il problema esposto non sarà risolto.

Nel frattempo accontentiamoci. Nel prossimo post analizzeremo la tecnica definitiva: l'analisi esadecimale dei log.