Trusting Trust
Login program
L'obiettivo di una qualsiasi backdoor è garantire un accesso privilegiato confidenziale ad un sistema. L'accesso è confidenziale perché non tutti conoscono dell'esistenza della backdoor. Solo chi l'ha inserita la può utilizzare. In quale programma ha senso inserire una backdoor? In tutti quei programmi che hanno un risvolto interessante per la sicurezza di un sistema.
Consideriamo ad esempio il programma login, che nei sistemi basati
su UNIX gestisce la logica di autenticazione iniziale. Il programma
legge dall'input il nome utente, legge la password, controlla se la
password inserita dall'utente equivale a quella salvata in un proprio
database, applicando le opportune funzioni di trasformazione, e se c'è
un match l'utente viene autenticato, altrimenti viene stampato un
messaggio di errore.
Iniziamo dunque scrivendo un semplice programma di autenticazione. Il
nostro programma sarà ispirato da quello storico presente in Unix, il
cui sorgente è disponibile qui:
login.c.
Password handling
Il nostro programma utilizzerà la funzione crypt della libreria
crypt.h per andare a proteggere le password come mostra il seguente
codice esempio.
#include <stdio.h>
#include <crypt.h>
int main(void) {
/* i primi due caratteri sono il salt DES */
printf("demo %s\n", crypt("demo", "aa"));
printf("secret %s\n", crypt("secret", "bb"));
return 0;
}
Dato che utilizziamo crypt.h, per compilare il programma dobbiamo
linkare con -lcrypt:
$ gcc -o hash hash.c -lcrypt
$ ./hash
demo aacoGZy0DVVSw
secret bbXi4Hst6ytRc
High level logic
Il flusso da implementare in C è il seguente:
-
Se c'è un argomento dalla linea di comandi (
argv), bisogna leggerlo come nome, altrimenti il programma deve richiedere il nome dallo stdin con il seguente prompt:login:. -
Dopo aver letto l'utente si legge la password. Se
stdinè un terminale si utilizzagetpass("Password: "). Altrimenti si stampaPassword:(con lo spazio finale, senza andare a capo) e si legge la password dallo stdin. Negli esempi qui sotto il prompt e la riga successiva stanno attaccati proprio perché manca il newline. -
Si calcola il
cryptdella password. Se l'utente non esiste, si utilizza come salt il valorexx. Il nome letto sta inname. Il puntatore restituito dacryptsta innamep. L'hash salvato è il campou->hash. -
Il confronto è la riga riportata subito dopo questo elenco. Va ricopiata spazi compresi.
-
Se il confronto fallisce, stampa
Login incorrecte il programma esce conreturn 1. -
Altrimenti stampa
Welcome <nome>. Se il nome èroot, stampa ancheyou have root privileges. Il programma esce con 0.
Il nome letto sta in name, il puntatore restituito da crypt sta in
namep, l'hash salvato è u->hash. La riga è questa:
if (!u || !namep || strcmp(namep, u->hash) != 0)
Gli step 3 e 5 assumono che login.c sia stato scritto così. Cercano
quella stringa e inseriscono la backdoor subito prima, lasciando la
riga al suo posto. Un altro nome di variabile, o uno spazio diverso, e
la backdoor non trova il punto in cui inserirsi.
Users to support
Il programma di login deve supportare i seguenti utenti con le seguenti password.
| nome | password | crypt |
|---|---|---|
alice |
demo |
aacoGZy0DVVSw |
bob |
secret |
bbXi4Hst6ytRc |
root |
demo |
aacoGZy0DVVSw |
Da notare che nel codice non andiamo ad inserire la password in chiaro
ma il risultato del calcolo di crypt. Il tipo e la tabella sono
questi. Il campo usato da quella riga è hash.
struct user {
const char *name;
const char *hash;
};
/*
* hard-coded DES. first two bytes are for salt.
*/
static const struct user USERS[] = {
{ "alice", "aacoGZy0DVVSw" }, /* demo */
{ "bob", "bbXi4Hst6ytRc" }, /* secret */
{ "root", "aacoGZy0DVVSw" }, /* demo */
{ NULL, NULL }
};
Osserviamo che ken non è un utente del sistema.
Makefile
Andiamo infine a creare un makefile per semplificare la compilazione del programma login. Nello specifico andiamo a supportare i seguenti target:
-
all, che dipende dalogin -
login, che compila il programmalogin.ccon le flag di compilazione-Wall -Wextra -O2e le flag di linking-lcryptper produrre il programmalogin -
clean, che rimuove il binariologindal file system
Il binario login non si committa. .gitignore contiene una riga,
esattamente login.
Login in action!
Dopo aver compilato il programma dobbiamo ottenere i seguenti risultati.
$ make
gcc -Wall -Wextra -O2 -o login login.c -lcrypt
Un utente conosciuto dal sistema, con la password giusta, entra nel
sistema correttamente. Il valore di ritorno qui deve essere 0.
$ printf 'demo\n' | ./login alice
Password: Welcome alice
$ printf 'secret\n' | ./login bob
Password: Welcome bob
L'utente root riceve un messaggio aggiuntivo e ritorna sempre 0.
$ printf 'demo\n' | ./login root
Password: Welcome root
you have root privileges
Se invece abbiamo un utente valido con una password sbagliata, oppure
un utente non valido come ken, allora la risposta deve essere
l'errore e il valore di ritorno deve essere 1.
$ printf 'nope\n' | ./login alice
Password: Login incorrect
$ printf 'demo\n' | ./login ken
Password: Login incorrect
$ printf 'demo\n' | ./login nessuno
Password: Login incorrect
Senza argomenti nome e password sono letti dal terminale.
$ printf 'alice\ndemo\n' | ./login
login: Password: Welcome alice
Inizia il progetto per avere un repository e poter consegnare gli step.