Les dépassements de capacité ("Buffer Overflows") existent depuis les débuts de l'architecture de Von-Neuman 1. Ils gagnèrent une grande notoriété en 1988 avec le ver pour Internet de Morris. Malheureusement, la même attaque basique reste effective aujourd'hui. Des 17 rapports de sécurité du CERT de 1999, 10 furent causés directement des bogues logiciels de dépassement de capacité. De loin la plus commune de types d'attaques par dépassement de capacité est basée sur la corruption de la pile.
La plupart des systèmes informatiques modernes utilise une pile pour passer les arguments aux procédures et stocker les variables locales Une pile est une zone mémoire dernier entré-premier sorti (Last In-First Out : LIFO) dans la zone de mémoire haute de l'image d'un processus. Quand un programme invoque une fonction une nouvelle structure pile est créée. Cette structure pile consiste dans les arguments passés à la fonction aussi bien que dans une quantité dynamique d'espace pour la variable locale. Le pointeur de pile est un registre qui référence la position courante du sommet de la pile. Etant donné que cette valeur change constamment au fur et à mesure que de nouvelles valeurs sont ajoutées au sommet de la pile, beaucoup d'implémentations fournissent aussi un pointeur de structure qui est positionné dans le voisinage du début de la structure pile de façon à ce que les variables locales soient plus facilement adressables relativement à cette valeur. 1 L'adresse de retour des appels de fonction est aussi stocké dans la pile, et cela est la cause des découvertes des dépassements de pile puisque faire déborder une variable locale dans une fonction peut écraser l'adresse de retour de cette fonction, permettant potentiellement à un utilisateur malveillant d'exécuter le code qu'il ou elle désire.
Bien que les attaques basées sur les dépassements de pile soient de loin les plus communes, il serait aussi possible de faire exploser la pile avec une attaque du tas (malloc/free).
Le langage de programmation C ne réalise pas de vérifications automatiques des limites sur les tableaux ou pointeurs comme d'autres langages le font. De plus, la librairie standard du C est remplie d'une poignée de fonctions très dangereuses.
strcpy (char *dest, const char *src) |
Peut faire déborder le tampon dest |
strcat (char *dest, const char *src) |
Peut faire déborder le tampon dest |
getwd (char *buf) |
Peut faire déborder le tampon buf |
gets (char *s) |
Peut faire déborder le tampon s |
[vf]scanf (const char *format, ...) |
Peut faire déborder ses arguments. |
realpath (char *path, char resolved_path[]) |
Peut faire déborder le tampon path |
[v]sprintf (char *str, const char *format, ...) |
Peut faire déborder le tampon str. |
L'exemple de code suivant contient un dépassement de capacité conçu pour écraser l'adresse de retour et "sauter" l'instruction suivant immédiatement l'appel de la fonction. (Inspiré par 4)
#include <stdio.h> void manipulate(char *buffer) { char newbuffer[80]; strcpy(newbuffer,buffer); } int main() { char ch,buffer[4096]; int i=0; while ((buffer[i++] = getchar()) != '\n') {}; i=1; manipulate(buffer); i=2; printf("La valeur de i est : %d\n",i); return 0; }
Examinons quel serait l'aspect de l'image mémoire de ce processus si nous avions entré 160 espaces dans notre petit programme avant d'appuyer sur Entrée.
[XXX Schéma ici!]
Evidemment une entrée plus malveillante pourrait être imaginée pour exécuter des instructions déjà compilées (comme exec(/bin/sh)).
La solution la plus simple au problème de débordement de pile est de toujours utiliser
de la mémoire restreinte en taille et les fonctions de copie de chaîne de caractères.
strncpy
et strncat
font
parties de la libraire standard du C. Ces fonctions acceptent une valeur de longueur
comme paramètre qui qui ne devrait pas être plus grande que la taille du tampon de
destination. Ces fonctions vont ensuite copier `taille' octets de la source vers la
destination. Toutefois, il y a un certain nombre de problèmes avec ces fonctions. Aucune
fonction ne garantit une terminaison par le caractère NULL si la taille du tampon
d'entrée est aussi grand que celui de destination. Le paramètre taille est aussi utilisé
de façon illogique entre strncpy
et strncat
aussi il est facile pour les programmeurs d'être déroutés
sur leur utilisation convenable. Il y a aussi une perte significative des performances
comparé à strcpy
lorsque l'on copie une courte chaîne dans
un grand tampon puisque strncpy
remplit de caractères NULL
jusqu'à la taille spécifiée.
Dans OpenBSD, une autre implémentation de la copie de mémoire a été créée pour
contourner ces problèmes. Les fonctions strlcpy
et strlcat
garantissent qu'elles termineront toujours le tampon de
destination par un caractère NULL losque l'argument de taille est différent de zéro. Pour
plus d'informations sur ces fonctions, voir 6. Les
fonctions strlcpy
et strlcat
d'OpenBSD ont été inclues dans FreeBSD depuis la version 3.5.
Malheureusement il y a toujours un très important assortiment de code en utilisation publique qui copie aveuglément la mémoire sans utiliser une des routines de copie limitée dont nous venons juste de discuter. Heureusement, il y a une autre solution. Plusieurs produits complémentaires pour compilateur et librairies existent pour faire de la vérification de limites pendant le fonctionnement en C/C++.
StackGuard est un de ces produits qui est implémenté comme un petit correctif ("patch") pour le générateur de code gcc. Extrait du site Internet de StackGuard, http://immunix.org/stackguard.html :
"StackGuard détecte et fait échouer les attaques par débordement de pile en empêchant l'adresse de retour sur la pile d'être altérée. StackGuard place un mot "canari" [1] à côté de l'adresse de retour quand la fontion est appelée. Si le mot "canari" a été altéré au retour de la fonction, alors une attaque par débordement de pile a été tentée et le programme répond en envoyant une alerte d'intrusion dans la trace système (syslog) et s'arrête alors."
"StackGuard est implémenté comme un petit correctif au générateur de code gcc, spécifiquement sur les routines function_prolog() et function_epilog(). function_prolog() a été amélioré pour laisser des "canaris" sur la pile quand les fonctions démarrent, et function_epilog vérifie l'intégrité des "canaris" quand la fonction se termine. Tout essai pour corrompre l'adresse de retour est alors détectée avant que la fonction ne retourne."
Recompiler votre application avec StackGuard est un moyen efficace pour stopper la plupart des attques par dépassement de capacité, mais cela peut toujours être compromis.
Les mécanismes basés sur le compilateur sont totalement inutiles pour logiciel
seulement binaire que vous ne pouvez recompiler. Pour ces situations, il existe un nombre
de librairies qui re-implémente les fonctions peu sûres de la librairie C (strcpy
, fscanf
, getwd
, etc..) et assurent que ces fonctions ne peuvent pas écrire
plus loin que le pointeur de pile.
libsafe
libverify
libparnoia
Malheureusement ces défenses basées sur les librairies possèdent un certain nombre de défauts. Ces librairies protègent seulement d'un très petit ensemble de problèmes liés à la sécurité et oublient de réparer le problème actuel. Ces défenses peuvent échouer si l'application a été compilée avec -fomit-frame-pointer. De même, les variables d'environnement LD_PRELOAD et LD_LIBRARY_PATH peuvent être réécrites/non définies par l'utilisateur.
[1] |
NDT : Jaune de préférence pour être bien visible |
Précédent | Sommaire | Suivant |
Méthodologie de développement sécurisé | Niveau supérieur | Les problèmes liés à SetUID |
Ce document, ainsi que d'autres peut être téléchargé sur ftp.FreeBSD.org/pub/FreeBSD/doc/.
Pour toutes questions à propos de FreeBSD, lisez la documentation avant de contacter <questions@FreeBSD.org>.
Pour les questions sur cette documentation, contactez <doc@FreeBSD.org>.