Il existe plusieurs solutions pour vérifier la qualité d'un mot de passe.
La première solution est de tester à postiriori les mots de passe stockés dans le système. Un des programmes les plus utilisé (parmis les nombreux existant) est "John the Ripper". Il permet de faire des attaques par dictionnaires et mutations sur les fichiers de mot de passe.
Rappel: les fichiers de mots de passe ne stockent pas les mots de passe mais le résultat de la transformation du mot de passe par une fonction a sens unique. John The Ripper support plusieurs fonctions à sens uniques (MD5, crypt, LM).
Question
Récuperez les fichiers passwd et shadow. Recréez un fichier passwd contenant les mots de passe shadow et les informations gecos en utilisant l'utilitaire unshadow de john. Créer un dictionnaire global en utilisant les dictionnaire de John et de cracklib.
Solution
unshadow passwd shadow > passwd_unshadowed john --rules --show passwd_unshadowed less all.gz cracklib-words.gz | sort | uniq > ourdict john --wordlist=ourdict --rules --show passwd_unshadowed john --wordlist=ourdict --rules --show passwd_ypcat
Déontologiquement, la vérification du mot de passe des utilisateurs reste quand même limite. A la place de tester les mots de passes déjà stockés dans le système, il reste possible de forcer les utilisateurs à saisir des mots de passe corrects.
Pour cela, il existe le programme cracklib (ainsi que sa librairie pam_cracklib). Ce programme permet de vérifier le mot de passe.
Question
Faites foncionner le cracklib_test pour tester les mots de passe saisie par les utilisateurs. Utilisez pour cela le dictionnaire généré précédemment.
Il existe une librairie pam_cracklib pour pam qui permet de vérifier la validiter du mot de passe. Quelle session du pam.d faudrait-il modifier pour ajouter cette librairie ?
Solution
create-cracklib-dict ourdict cracklib-check Choix possible: auth, account, password, session A vérifier dans le etc/pam.d/system-auth. On ajoute en première ligne de password : password required pam_cracklib.so retry=3 minlen=4 dcredit=0 ucredit=0
Voir dans /etc/pam.d et regarder les différents fichiers.
pam_start qui va initialisation un handler d'authentification (pam_handle_t). Vous utiliserez la structure pam_conv suivante :
static struct pam_conv conv = { misc_conv, NULL };
pam_set_item pour fixer les paramètres de l'authentification
pam_authenticate pour effectuer l'authentification (le handler mémorisera les informations d'authentification).
pam_acct_mgmt pour savoir si le mot de passe doit être changé.
pam_setcred pour fixer les droits utilisateurs. Rappel: Seul root peut changer d'identité (voir les bits suid de passwd, su, ...).
Question
Ecrire un programme qui utilise le service PAM pour s'authentifier et exécuter la commande passée en paramètre avec les droits de l'utilisateur donné en paramètre.
Solution
/* * Andrew Morgan (morgan@kernel.org) -- an example application * that invokes a shell, based on blank.c * modifie par Fabrice Legond */ #include <stdio.h> #include <stdlib.h> #include <security/pam_appl.h> #include <security/pam_misc.h> #include <pwd.h> #include <sys/types.h> #include <unistd.h> /* ------ some local (static) functions ------- */ static void bail_out(pam_handle_t *pamh,int really, int code, const char *fn) { fprintf(stderr,"==> called %s()\n got: `%s'\n", fn, pam_strerror(pamh,code)); if (really && code) exit (1); } /* ------ some static data objects ------- */ static struct pam_conv conv = { misc_conv, NULL }; /* ------- the application itself -------- */ int main(int argc, char **argv) { pam_handle_t *pamh=NULL; const void *username="nobody"; const char *service="system-auth"; const char *program="/bin/false"; int retcode; /* did the user call with a username as an argument ? * did they also */ if (argc != 4) { fprintf(stderr,"usage: %s username service-name command\n",argv[0]); exit(1); } username = argv[1]; service = argv[2]; program = argv[3]; /* initialize the Linux-PAM library */ retcode = pam_start(service, username, &conv, &pamh); bail_out(pamh,1,retcode,"pam_start"); /* fill in the RUSER and RHOST etc. fields */ { char buffer[100]; struct passwd *pw; const char *tty; pw = getpwuid(getuid()); if (pw != NULL) { retcode = pam_set_item(pamh, PAM_RUSER, pw->pw_name); bail_out(pamh,1,retcode,"pam_set_item(PAM_RUSER)"); } retcode = gethostname(buffer, sizeof(buffer)-1); if (retcode) { perror("failed to look up hostname"); retcode = pam_end(pamh, PAM_ABORT); bail_out(pamh,1,retcode,"pam_end");} retcode = pam_set_item(pamh, PAM_RHOST, buffer); bail_out(pamh,1,retcode,"pam_set_item(PAM_RHOST)"); tty = ttyname(fileno(stdin)); if (tty) { retcode = pam_set_item(pamh, PAM_TTY, tty); bail_out(pamh,1,retcode,"pam_set_item(PAM_RHOST)"); } } /* to avoid using goto we abuse a loop here */ for (;;) { /* authenticate the user --- `0' here, could have been PAM_SILENT * | PAM_DISALLOW_NULL_AUTHTOK */ retcode = pam_authenticate(pamh, 0); bail_out(pamh,0,retcode,"pam_authenticate"); /* has the user proved themself valid? */ if (retcode != PAM_SUCCESS) { fprintf(stderr,"%s: invalid request\n",argv[0]); break; } /* the user is valid, but should they have access at this time? */ retcode = pam_acct_mgmt(pamh, 0); /* `0' could be as above */ bail_out(pamh,0,retcode,"pam_acct_mgmt"); if (retcode == PAM_NEW_AUTHTOK_REQD) { fprintf(stderr,"Application must request new password...\n"); retcode = pam_chauthtok(pamh,PAM_CHANGE_EXPIRED_AUTHTOK); bail_out(pamh,0,retcode,"pam_chauthtok"); } if (retcode != PAM_SUCCESS) { fprintf(stderr,"%s: invalid request\n",argv[0]); break; } /* `0' could be as above */ retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); bail_out(pamh,0,retcode,"pam_setcred"); if (retcode != PAM_SUCCESS) { fprintf(stderr,"%s: problem setting user credentials\n" ,argv[0]); break; } /* open a session for the user --- `0' could be PAM_SILENT */ retcode = pam_open_session(pamh,0); bail_out(pamh,0,retcode,"pam_open_session"); if (retcode != PAM_SUCCESS) { fprintf(stderr,"%s: problem opening a session\n",argv[0]); break; } pam_get_item(pamh, PAM_USER, &username); fprintf(stderr, "The user [%s] has been authenticated and `logged in'\n", (const char *)username); /* this is always a really bad thing for security! */ retcode = system(program); /* close a session for the user --- `0' could be PAM_SILENT * it is possible that this pam_close_call is in another program.. */ retcode = pam_close_session(pamh,0); bail_out(pamh,0,retcode,"pam_close_session"); if (retcode != PAM_SUCCESS) { fprintf(stderr,"%s: problem closing a session\n",argv[0]); break; } /* `0' could be as above */ retcode = pam_setcred(pamh, PAM_DELETE_CRED); bail_out(pamh,0,retcode,"pam_setcred"); if (retcode != PAM_SUCCESS) { fprintf(stderr,"%s: problem deleting user credentials\n" ,argv[0]); break; } break; /* don't go on for ever! */ } /* close the Linux-PAM library */ retcode = pam_end(pamh, PAM_SUCCESS); pamh = NULL; bail_out(pamh,1,retcode,"pam_end"); return (0); }
La Xlib est la bibliothèque graphique standard d'Unix. Une de ses originalités est d'etre fondée sur une architecture client / serveur distribuée, ce qui permet à tout client Internet de demander l'ouverture de fenetres et autres services apparentés sur a priori n'importe quelle machine du réseau. Cette généralité (qui inclut meme le verrouillage du clavier du poste concerné !) est évidemment tempérée par une authentification invisible mais bien réelle. La présente séance consiste à expliciter cette authentification, afin de comprendre les mécanismes mis en jeu et leurs éventuelles limitations.
En trouvera en annexe un fichier main.c ainsi qu'un makefile référençant celui-ci et deux fichiers request.c et receive.c qu'il s'agit d'écrire, ainsi qu'un petit shell-script de test. On rappelle que dans un makefile, un but est un symbole au début d'une ligne comportant un deux-points, et les sources ce qui suit ce deux-points. Le but s'obtient des sources en exécutant les commandes shell écrites dans les lignes suivantes, jusqu'à rencontre d'une ligne ne commençant pas par une tabulation. Le symbole % permet d'énoncer plusieurs dépendances à la fois ; en particulier, %.o : %.c énonce que tout but suffixé par .o a pour source le fichier homonyme de suffixe .c. Les variables @ et < ; désignent respectivement le but courant et sa première source.
Le programme C lance une fonction main tentant une connexion avec le serveur X-window donné en argument, ou à défaut au serveur local standard (adresse 127.0.0.1, port 6000). Une fonction showbuffer est incluse pour visualiser les messages transmis, ainsi qu'une fonction peroraison pour déclencher des messages d'erreur. Le protocole X-window n'est ni textuel ni synchrone : les clients envoient des flux d'octets structurés différement selon les requêtes (et selon leur byte-ordering), et prennent connaissance des réponses ou des notifications d'événements quand ils l'estiment nécessaire.
Écrire la fonction request (dans un fichier C homonyme), invoquée par main après un connect réussi. Son rôle est d'envoyer au serveur la requête d'ouverture, ainsi structurée :
Numéro | Nombre | Valeur | Description |
0 | 1 | B ou l | B pour gros-boutien, l pour petit-boutien |
1 | 1 | ; | inutilisé |
2 | 2 | [1,11] | numéro de version |
4 | 2 | ; | numéro de sous-version |
6 | 2 | n | longueur du nom de la méthode d'identification |
8 | 2 | d | longueur de la clé d'identification |
10 | 2 | ; | inutilisé |
12 | n | ; | nom de la méthode d'identification |
12+n | c(n) | ; | cadrage pour alignement |
12+n+c(n) | d | ; | clé d'identification |
12+n+c(n)+d | c(d) | ; | cadrage pour alignement |
Question
Ecrire une définition provisoire de receive comme ne faisant rien, afin de tester en l'état, sans donner aucun argument au binaire produit.
Solution
#define HEX2BIN(c)\ (((c) < 'A') ? ((c) -'0') : (((c) < 'a') ? ((c) -'A' +10) : ((c) -'a'+10))) #define GULLIVER 'l' void request (int desc, char *auth_name, char *auth_data) int i, c=12 ; char xopen[128] ; xopen[0]=GULLIVER ; xopen[1]=0 ; // insignifiant ; xopen[2]=11 ; // major version xopen[3]=0 ; xopen[4]=0 ; // minor version xopen[5]=0 ; xopen[6]=strlen(auth_name) ; xopen[7]=0 ; xopen[8]=strlen(auth_data)>>1 ; xopen[9]=0 ; xopen[10]=0 ; // insignifiant ; xopen[11]=0 ; // insignifiant ; strcpy(xopen+12,auth_name) ; c+=strlen(auth_name) ; printf("%s\n", auth_data) ; while (c&11) { xopen[c]=0 ; c++ ; } for (i=0 ;i< xopen[8] ; i++,c++,auth_data+=2) { xopen[c]=HEX2BIN(*auth_data) ; xopen[c]<<=4 ; xopen[c]+=(HEX2BIN(*(auth_data+1))) ; } while (c&11) { xopen[c]=0 ; c++ ; } printf("j'envoie %d octets :", c) ; showbuffer(xopen,c) ; if (write(desc, xopen, c) <=0) peroraison("write","erreur d'écriture sur le socket ") ;
La fonction receive, invoquée par main, va recevoir un flux commençant par les 8 octets suivants :
Numéro | Nombre | Valeur | Description |
0 | 1 | 0 ou 1 | erreur ou réusite |
1 | 1 | n | longueur du message d'erreur, inutilisé sinon |
2 | 2 | [1,11] | numéro de version |
4 | 2 | ; | numéro de sous-version |
6 | 2 | n/4+((n%4)!=0) | longueur en mots de 32 bits de la suite |
Question
Écrire d'abord une version réduire de la fonction receive, testant si les 8 octets reçus dénotent une erreur, et si oui, affichant le message d'erreur (ce sont les octets suivants, moins les octets de cadrage).
En cas de réussite de la connexion, les 8 premiers octets sont suivis des 32 suivants :
Numéro | Nombre | Valeur | Description |
0 | 4 | ; | numéro de série |
4 | 4 | < ; 2^29 | identificateur de création |
8 | 4 | plus de 18 '1' consécutifs | masque de création |
12 | 4 | ; | nombre de mouvements mémorisés |
16 | 2 | c | longueur du nom du constructeur |
18 | 2 | ; | longueur maximale d'une requête |
20 | 1 | n | nombre d'écrans |
21 | 1 | ; | nombre de formats |
22 | 1 | ; | ordre des octets |
23 | 1 | ; | ordre des bits |
24 | 1 | ; | format des bits |
25 | 1 | ; | cadrage des bits |
26 | 1 | ; | plus petit code des touches |
27 | 1 | ; | plus grand code des touches |
28 | 4 | ; | inutilisé |
32 | c | ; | nom du constructeur |
32+c | ; | ; | descriptions des formats et écrans |
Question
Compléter la fonction receive pour qu'en cas de réussite, elle affiche le nom du constructeur et le nombre d'écran.
Solution
int receive (int desc) { unsigned char buf[4096] ; char *p ; int c,i ; c=read(desc, buf, 8) ; if (c !=8) peroraison("read", "la socket répond mal") ; if ( !(buf[0])) { i=buf[1] ; printf("Rejet de la demande de connection, %d\n",i) ; read(desc, buf, i) ; buf[i]=0 ; puts(buf) ; return(-1) ; } else { c=((buf[7]<<4)+buf[6]-8)<<2 ; showbuffer(buf,8) ; printf(" : huit premiers octets reçus ; %d octets à venir\n",c) ; c=read(desc, buf+8, c) ; if (c <0) { peroraison("read", "la socket poursuit mal") ; c+=8 ; printf("j'ai reçu %d octets au total.\n",c) ; i=((buf[25]<<4)+buf[24]) ; p=buf+40 ; printf("Voici le constructeur du serveur :\n",i) ; for(;i;i-,p++) putchar(*p) ; printf("\nVoici le nombre d'écran : %d\n",*(buf+28)) ; showbuffer(buf,c) ; return(0) ; }
Pour donner à ce programme les arguments nécessaires à une connexion réussie, il faut connaître la méthode d'identification et sa clé. Elles se trouvent dans le fichier .Xauthority. Les clés étant des valeurs binaires, le fichier est peu lisible, aussi on utilisera la commande xauthlist.
Question
Repérer votre dispositif dans le résultat, et utiliser les informations associées pour réussir votre connexion (attention ! il faut convertir en binaire les valeurs hexadécimales affichées en ascii par xauth).
Solution
La méthode utilisée la plupart du temps est MIT-MAGIC-COOKIE-1.
Question
Écrire le script etquecasaute qui produit automatiquement les arguments nécessaires au programme. Consulter la documentation sur xauth pour cela, et employer l'utilitaire sed pour effacer les informations inutiles.
Solution
echo 6002 `xauth list $DISPLAY | sed "s,/.* :, :, ;s/ :[0-9]*/ /"`
A ce stade, le programme écrit est assez proche de la commande xdpyinfo, il est inutile d'aller plus loin dans cette direction. Discuter en revanche les limites de cette méthode :
- en ce qui concerne les droits sur le fichier .Xauthority
- en ce qui concerne la surveillance du réseau par un tiers.
Question
En conclure qu'il est de nos jour impératif d'utiliser l'option -X de la commande ssh.
Solution
Le fichier {/.Xauthority} ne doit évidemment pas être accessible en lecture par autrui.
La surveillance du réseau par un simple visionneur TCP suffit à repérer cet identifiant.
L'option -X de ssh crypte les échanges entre le client (la machine sur laquelle on se connecte) et le serveur X-window (résidant sur la machine d'où l'on lance ssh). Cette commande crée un {.Xauthority} sur le client avec un pseudo-dispositif qui est en fait l'adresse de la socket où s'effectue la connexion cryptée au serveur.