Aggiungere il supporto shadow ad un programma è in realtà abbastanza semplice. L'unico problema è che il programma deve essere eseguito da root (o SUID root) in modo che il programma possa accedere al file /etc/shadow
.
Questo presenta un grande problema: occorre seguire una condotta di programmazione molto attenta quando si creano programmi SUID. Per esempio, se un programma ha un comando che invoca una shell, questa non deve essere eseguita con i diritti di root anche se il programma è SUID root.
Per aggiungere il supporto shadow ad un programma in modo che possa controllare le password, ma per il resto non necessita di essere eseguito da root, è molto più sicuro eseguire il programma SGID shadow. Il programma xlock
ne è un esempio.
Nell'esempio fatto prima, pppd-1.2.1d
già viene eseguito SUID root, perciò aggiungere il supporto shadow non dovrebbe rendere il programma più vulnerabile.
I file di intestazione (header) dovrebbero stare in /usr/include/shadow
. Ci dovrebbe anche essere un /usr/include/shadow.h
, ma sarebbe un link simbolico a /usr/include/shadow/shadow.h
.
Per aggiungere il supporto shadow ad un programma, dovete includere i file di intestazione:
#include <shadow/shadow.h> #include <shadow/pwauth.h>
Potrebbe essere una buona idea usare le direttive del compilatore in modo da condizionare la compilazione del codice shadow (io lo faccio nell'esempio che segue).
Quando avete installato la Shadow Suite il file libshadow.a
è stato creato ed installato in /usr/lib
.
Quando si compila il supporto shadow in un programma, bisogna dire al linker di includere la libreria libshadow.a
.
Questo viene fatto da:
gcc program.c -o program -lshadow
Comunque, come vedremo nell'esempio che segue, la maggior parte dei programmi di grandi dimensioni usa un Makefile
, che di solito ha una variabile chiamata LIBS=...
che noi modificheremo.
La libreria libshadow.a
usa una struttura chiamata spwd
per le informazioni che preleva dal file /etc/shadow
. Questa è la definizione della struttura spwd
dal file di intestazione /usr/include/shadow/shadow.h
:
struct spwd { char *sp_namp; /* nome di login */ char *sp_pwdp; /* password codificata */ sptime sp_lstchg; /* data dell'ultimo cambiamento */ sptime sp_min; /* minimo numero di giorni tra cambiamenti */ sptime sp_max; /* massimo numero di giorni tra cambiamenti */ sptime sp_warn; /* numero di giorni di avvertimento prima che scada la password */ sptime sp_inact; /* numero di giorni dopo la scadenza della password prima che l'account venga disabilitato */ sptime sp_expire; /* giorni dal 1/1/70 fino alla scadenza dell'account */ unsigned long sp_flag; /* riservato per uso futuro */ };
La Shadow Suite può mettere altre cose nel campo sp_pwdp
proprio a fianco della password codificata. Il campo della password potrebbe contenere:
nomeutente:Npge08pfz4wuk;@/sbin/extra:9479:0:10000::::
Questo significa che, oltre alla password, dovrebbe essere chiamato il programma /sbin/extra
per ulteriori autenticazioni. Il programma chiamato riceverà il nome utente e un'opzione che indica perché viene chiamato. Vedere il file /usr/include/shadow/pwauth.h
e il codice sorgente di pwauth.c
per ulteriori informazioni.
Ciò che voglio dire è che dovremmo usare la funzione pwauth
per eseguire la vera autenticazione, dato che si occuperà anche dell'autenticazione secondaria. L'esempio sotto fa proprio questo.
L'autore della Shadow Suite fa presente che poiché molti dei programmi esistenti non la usano potrebbe essere rimossa o cambiata dalle future versioni della Shadow Suite.
Il file shadow.h
contiene anche i prototipi delle funzioni contenute nella libreria libshadow.a
:
extern void setspent __P ((void)); extern void endspent __P ((void)); extern struct spwd *sgetspent __P ((__const char *__string)); extern struct spwd *fgetspent __P ((FILE *__fp)); extern struct spwd *getspent __P ((void)); extern struct spwd *getspnam __P ((__const char *__name)); extern int putspent __P ((__const struct spwd *__sp, FILE *__fp));
La funzione che useremo nell'esempio è: getspnam
che ritorna una struttura spwd
per il nome passato per argomento.
Questo è un esempio di aggiunta del supporto shadow ad un programma che ne ha bisogno, ma non lo possiede.
Questo esempio usa il Point-to-Point Protocol Server (pppd-1.2.1d), che ha una modalità in cui esegue l'autenticazione PAP usando i nomi e le password degli utenti dal file /etc/passwd
anziché dai file PAP o CHAP. Non dovreste aver bisogno di aggiungere questo codice a pppd-2.2.0
perché c'è già.
Questa caratteristica del pppd probabilmente non è molto usata, ma se avete installato la Shadow Suite, non funzionerà comunque perché le password non si trovano più in /etc/passwd
.
Il codice per l'autenticazione degli utenti sotto pppd-1.2.1d
si trova nel file /usr/src/pppd-1.2.1d/pppd/auth.c
.
Il seguente codice deve essere aggiunto all'inizio del file dove si trovano tutte le altre direttive #include
. Abbiamo racchiuso gli #include
tra direttive condizionali (i.e. vengono presi in considerazione solo se stiamo compilando per il supporto shadow).
#ifdef HAS_SHADOW #include <shadow.h> #include <shadow/pwauth.h> #endif
Il passo successivo consiste nel modificare il codice vero e proprio. Stiamo ancora apportando cambiamenti al file auth.c
.
Funzione auth.c
prima delle modifiche:
/* * login - Controlla il nome e la password dell'utente nel database delle * password di sistema, e permette il login se l'utente è OK. * * restituisce: * UPAP_AUTHNAK: Login fallito. * UPAP_AUTHACK: Login riuscito. * In entrambi i casi, msg punta al messaggio appropriato. */ static int login(user, passwd, msg, msglen) char *user; char *passwd; char **msg; int *msglen; { struct passwd *pw; char *epasswd; char *tty; if ((pw = getpwnam(user)) == NULL) { return (UPAP_AUTHNAK); } /* * XXX Se non c'è nessuna password, li lascia collegare senza. */ if (pw->pw_passwd == '\0') { return (UPAP_AUTHACK); } epasswd = crypt(passwd, pw->pw_passwd); if (strcmp(epasswd, pw->pw_passwd)) { return (UPAP_AUTHNAK); } syslog(LOG_INFO, "user %s logged in", user); /* * Scrive una voce wtmp per questo utente. */ tty = strrchr(devname, '/'); if (tty == NULL) tty = devname; else tty++; logwtmp(tty, user, ""); /* Aggiunge una voce di login al wtmp */ logged_in = TRUE; return (UPAP_AUTHACK); }
La password dell'utente viene messa in pw->pw_passwd
, così tutto quello che dobbiamo fare in realtà è aggiungere la funzione getspnam
. Questa metterà la password in spwd->sp_pwdp
.
Aggiungeremo la funzione pwauth
per eseguire l'autenticazione vera e propria.
Questa eseguirà automaticamente l'autenticazione secondaria se il file shadow è impostato per farlo.
Funzione auth.c
dopo le modifiche per il supporto shadow:
/* * login - Controlla il nome e la password dell'utente nel database delle * password di sistema, e permette il login se l'utente è OK. * * Questa funzione è stata modificata in modo da supportare la * Linux Shadow Password Suite se USE_SHADOW è definito. * * restituisce: * UPAP_AUTHNAK: Login fallito. * UPAP_AUTHACK: Login riuscito. * In entrambi i casi, msg punta al messaggio appropriato. */ static int login(user, passwd, msg, msglen) char *user; char *passwd; char **msg; int *msglen; { struct passwd *pw; char *epasswd; char *tty; #ifdef USE_SHADOW struct spwd *spwd; struct spwd *getspnam(); #endif if ((pw = getpwnam(user)) == NULL) { return (UPAP_AUTHNAK); } #ifdef USE_SHADOW spwd = getspnam(user); if (spwd) pw->pw_passwd = spwd->sp-pwdp; #endif /* * XXX Se non c'è nessuna password, NON li lascia collegare senza. */ if (pw->pw_passwd == '\0') { return (UPAP_AUTHNAK); } #ifdef HAS_SHADOW if ((pw->pw_passwd && pw->pw_passwd[0] == '@' && pw_auth (pw->pw_passwd+1, pw->pw_name, PW_LOGIN, NULL)) || !valid (passwd, pw)) { return (UPAP_AUTHNAK); } #else epasswd = crypt(passwd, pw->pw_passwd); if (strcmp(epasswd, pw->pw_passwd)) { return (UPAP_AUTHNAK); } #endif syslog(LOG_INFO, "user %s logged in", user); /* * Scrive una voce wtmp per questo utente. */ tty = strrchr(devname, '/'); if (tty == NULL) tty = devname; else tty++; logwtmp(tty, user, ""); /* Aggiunge una voce di login al wtmp */ logged_in = TRUE; return (UPAP_AUTHACK); }
Un attento esame rivelerà che abbiamo fatto un'altra modifica. La versione originale permetteva l'accesso (restituiva UPAP_AUTHACK
) se non c'era NESSUNA password nel file /etc/passwd
. Questo non è una buona cosa, perché un uso comune di questa caratteristica di login è quello di usare un account che permetta l'accesso al processo ppp e quindi confrontare il nome utente e la password forniti da PAP con il nome utente nel file /etc/passwd
e la password nel file /etc/shadow
.
Perciò se abbiamo impostato la versione originale in modo da eseguire, al posto della shell per un utente, ad esempio ppp
, allora chiunque potrebbe ottenere una connessione ppp impostando la sua PAP con utente ppp
e senza password.
Abbiamo risolto questo anche restituendo UPAP_AUTHNAK
invece che UPAP_AUTHACK
nel caso in cui il campo password fosse vuoto.
È abbastanza interessante il fatto che pppd-2.2.0
abbia lo stesso problema.
Poi abbiamo bisogno di modificare il Makefile in modo che avvengano due cose:
USE_SHADOW
deve essere definita, e libshadow.a
deve essere aggiunta al processo di link.
Editate il Makefile, e aggiungete:
LIBS = -lshadow
Quindi troviamo la riga:
COMPILE_FLAGS = -I.. -D_linux_=1 -DGIDSET_TYPE=gid_t
E la cambiamo in:
COMPILE_FLAGS = -I.. -D_linux_=1 -DGIDSET_TYPE=gid_t -DUSE_SHADOW
Ora eseguite il make ed installate.