back to top

La sicurezza in Java: il modello Sandbox

Sin dalla sua introduzione, la tecnologia Java ha riservato un importante ruolo alle tematiche di sicurezza. Sebbene con esiti alterni, i progettisti hanno cercato di fornire la piattaforma di sistemi di sicurezza implementati direttamente a livello di linguaggio, disponibili agli sviluppatori.

In un continuo processo di evoluzione e raffinamento la JVM è divenuta una delle più importanti infrastrutture per applicazioni stand-alone, web, mobile e altro ancora; in questo articolo ci focalizzeremo sull’evoluzione del modello di sicurezza, comunemente detto a sandbox, commentandone gli errori di progettazione e lo stato attuale.

Prerequisiti

Questo articolo è rivolto a tutti, siano essi esperti programmatori Java che lettori totalmente digiuni dell’argomento. Alcune considerazioni saranno maggiormente comprensibili a coloro che possiedano già una minima esperienza con questo linguaggio, ciononostante la lettura di questo articolo è adatta a qualunque lettore.

La sandbox

Il modello originale

Il modello originale, noto come sandbox, fu progettato per confinare codice potenzialmente dannoso in un ambiente isolato e fortemente restrittivo. Java, sin dalla sua nascita, fu fortemente orientato alla rete e questa considerazione spinse ad ideare un modello di esecuzione in cui il codice veniva direttamente scaricato da remoto, esponendo il client a notevoli problematiche di sicurezza.

Nella sua prima implementazione, schematizzata in figura, la sandbox distingueva grossolanamente solo tra codice locale e codice remoto: il primo godeva di accesso completo a tutte le risorse "critiche" del sistema quali, ad esempio, il filesystem e i vari devices; il codice remoto, al contrario, aveva un limitato accesso alle risorse, mediato dalla sandbox stessa: le applet, ormai sostanzialmente scomparse dal panorama web, ne sono stato il più noto esempio.

Modello di sicurezza a sandbox presente nel JDK 1.0

Questo modello include una serie di meccanismi di sicurezza a diverso livello. Prima di tutto, Java è un linguaggio type-safe, cioè esiste una relazione esplicita e controllata tra una variabile e il suo tipo (intero, virgola mobile, stringa, ecc..). Coloro che hanno programmato in linguaggi a basso/medio livello quali il C e il C++ conoscono quanti problemi possa evitare questo controllo: quell’insieme di conversioni implicite tra tipi, ad esempio interi a booleani o puntatori void ad altri puntatori, che sono caratteristiche di quei linguaggi divengono al tempo stesso la principale sorgente di errori di programmazione, ugualmente commessi sia da principianti sia da esperti. Per minimizzare le possibilità che gli sviluppatori compiessero strafalcioni, i progettisti della Sun introdussero alcuni aspetti fino ad allora presenti solo in linguaggi di nicchia o a livello universitario quali, ad esempio, la gestione automatica della memoria (il garbage collector) e i controlli a run-time sull’accesso in memoria (a puntatori, elementi di array, ecc…).

Il secondo livello di protezione è garantito dal compilatore e, a run-time, dalla virtual machine. Ciò assicura che il bytecode, l’assembler della VM Java, sia eseguito con gli appropriati permessi di esecuzione. In particolare due componenti fondamentali, il ClassLoader e il SecurityManager, definiscono uno spazio di nomi locale per evitare interferenze tra diverse istanze della VM e gestiscono i controlli di accesso alle risorse critiche.

JDK 1.1 – Codice firmato

Il modello presentato è poco flessibile e già nel primo update del JDK (la versione 1.1) fu introdotto il concetto di codice trusted per permettere ad applicazioni remote, se corredate di una firma elettronica riconosciuta dal client, di accedere alle risorse di sistema. La soluzione, schematizzata nella figura seguente, è poco più di un hack della precedente architettura e pertanto necessiterà una completa riscrittura nelle successive release.

Modello di sicurezza a sandbox presente nel JDK 1.1

Java 2 – Un modello evoluto

Java 2 rappresenta una svolta per l’intera piattaforma Sun: sono gradualmente introdotti o riscritti da zero concetti quali i generici, la reflection, la gestione della concorrenza; anche l’architettura di sicurezza subisce una completa rivoluzione, come schematizzato in figura.

Evoluzione del modello di sicurezza nella piattaforma Java 2

Per rispondere alle pressanti richieste degli sviluppatori, Sun introduce un modello di controllo a maglie fini facilmente personizzabile; tramite il concetto di policy è possibile definire i permessi di un determinato contesto in maniera molto più espressiva di quanto permesso dal limitato e statico concetto di sandbox.

In ogni caso, la modifica più evidente riguarda l’estensione dei controlli a tutto il codice, sia esso un’applicazione locale o un’applet remota. Sun abbandona l’erroneo concetto che il codice locale sia da considerare trusted: ogni classe, escluse le librerie della jvm, è soggetta ai medesimi controlli attribuiti precedentemente alle sole applet; naturalmente l’utente ha la possibilità di abilitare una policy sul codice locale permissiva quanto quella presente nel vecchio modello.

Domini di protezione

Ora che abbiamo esplorato, seppure senza sviscerarne i dettagli, l’architettura a sandbox della JVM e la sua evoluzione nel corso delle varie release, ne introdurremo il meccanismo alla base della nuova architettura.

Un concetto fondante nel rinnovato sistema di sicurezza introdotto con Java 2 è quello del dominio di protezione; un dominio di protezione è, in sostanza, un insieme di oggetti accedibili da un’entità del sistema secondo certi permessi: la sandbox presente nella prima versione del JDK, che abbiamo precedentemente discusso, è un esempio di dominio di protezione in cui i permessi sono pre-impostati. Il dominio di protezione è dunque un livello di astrazione conveniente per raggruppare ed isolare le diverse unità operative limitandone le interazioni. I domini di protezione sono catalogabili secondo due diverse categorie, domini di sistema e domini applicativi; tutte le risorse critiche esterne alla JVM appartengono al primo insieme: accesso al file-system, alla rete e alle periferiche di Input/Output (I/O) ricadono in questa classificazione.

Operativamente un dominio di protezione racchiude un insieme di classi le cui istanze condividono le medesime regole, gli stessi permessi. Quest’ultimi sono definiti dalla policy associata al dominio, di cui la JVM si incarica di mantenerne il mapping con le classi e gli oggetti istanziati.

Consideriamo, ad esempio, un processo che stampi in standard output la stringa "Hello world!"; anche un novizio del linguaggio Java certamente riconoscerà la seguente chiamata a metodo:

System.out.println("Hello world!");

Quello che invece la maggior parte degli sviluppatori ignora è che una chiamata diretta all’oggetto statico out presente nella classe java.lang.System implica un escalation di privilegi da un dominio applicativo (quello a cui appartiene il processo) al dominio di sistema incaricato di gestirne l’output. In questa fase è cruciale che il dominio chiamante acquisisca solo i privilegi strettamente necessari all’operazione e le rilasci immediatamente conclusa la chiamata.

Immaginiamo ora un caso inverso, quando il dominio di sistema accede ad un dominio applicativo; questo accade, per esempio, quando il sottosistema grafico AWT invoca il metodo paint su una finestra dell’interfaccia grafica. In questa situazione è fondamentale che il sistema tracci i privilegi del dominio applicativo attivo in modo che le attività eseguite dal dominio di sistema siano limitate ai permessi posseduti. In altre parole, un dominio con minori abilitazioni non può accreditarsi di nuovi permessi come effetto di invocare (o essere invocato) da un dominio meno restrittivo.

Questo si generalizza, ovviamente, al caso in cui i domini di protezione siano in numero maggiore di due; le seguenti regole permettono di determinare i privilegi in uso:

  • l’insieme dei permessi di un determinato thread è rappresentato dall’intersezione di tutti i domini di protezione attraverso cui sta operando il thread;
  • quando una parte di codice richiede un escalation di privilegi, attraverso il metodo doPrivilege, la chiamata è eseguita se e solo se permesso dal dominio di protezione del codice chiamante e da tutti i domini di protezione che saranno, direttamente o indirettamente, interessati dalla chiamata.

Il metodo doPrivilege abilita una parte di codice ad accedere temporaneamente a risorse a cui non avrebbe accesso direttamente. Ad esempio, qualora durante l’esecuzione sia richiesto accesso alla rete o al file-system, questo metodo permette di transitare la chiamata sino alla classe di sistema AccessController che ne valuta la richiesta. Tuttavia si noti che la modalità operativa in cui è valutata tale richiesta varia tra le diverse implementazioni della JVM.

Conclusioni

Nonostante l’architettura rilasciata nelle prime versioni del JDK fosse facilmente criticabile e grossolana, le successive implementazioni hanno riscritto completamente i meccanismi di sicurezza rendendoli estremamente flessibili. Sebbene l’approccio per gli sviluppatori possa apparire complesso, questa soluzione rappresenta un ottimo compromesso tra sicurezza ed usabilità e, con un pò di pratica, accessibile a tutti. In definitiva, ciò permette agli sviluppatori di implementare in perfetta autonomia gli specifici meccanismi di sicurezza richesti dalla propria applicazione, limitandone l’utilizzo al proprio dominio applicativo.

Riferimenti e Approfondimenti

Pubblicitร 

Leggi anche...

Autenticazione a due fattori (2FA): cos’è e come funziona

L'autenticazione a due fattori (in inglese Two Factor Authentication...

Authcode: cos’è e come funziona

Con il termine Authcode (o Auth-code) si fa riferimento...

HTTP Security Headers: aumentare la sicurezza del sito con .htaccess

Esistono diverse tecniche per innalzare il livello di sicurezza...

Criptare (e decriptare) file su Linux con OpenSSL

OpenSSL è un'implementazione rilasciata sotto licenza Open Source dei...

Cos’è una Botnet?

Con il termine botnet si fa riferimento ad una...

DDoS: cos’è, come funziona e come difendersi

Un DDoS (acronimo di Distribuited Denial of Service) รจ...
Pubblicitร