Failles de sécurité
Débordements de tampons
int save() {
FILE* file = NULL;
char filename[256];
do {
printf("Entrez le nom de la sauvegarde : ");
fflush(stdout);
} while (gets(filename) == NULL);
file = fopen(filename, "rw");
...
}
On suppose ici que le nom de fichier est au plus 256 caractères. Or en réalité rien n'empêche l'utilisateur d'entrer un chaîne de caractères avec plus de 256 caractères.
Le risque est gets
aille écrire au delà de la zone réservée pour filename
. Elle peut en particulier écrire sur l'adresse de retour de la fonction save
.
Pire, un attaquant pourrait se débrouiller que cette adresse de retour soit le contenu du fichier file
dans lequel il aura écrit les actions qu'il désire.
Débordement sur le tas
C'est la même idée mais maintenant, on va écrire au delà d'un bloc allouée. On peut alors toucher des informations de gestion de la mémoire (la liste doublement chaînée de la mémoire).
String-format vulnerabilities
int i;
printf("blip%n", &i);
D'habitude printf
ne fait que écrire dans la console mais ne modifie pas de variables. En fait, si, avec %n
on écrit dans un entier le nombre de caractères déjà écrit sur la console.
Supposons qu'un attaquant contrôle la chaîne de caractères de formatage. Soit trivialement avec :
int i;
printf(stringControléeParLeMéchant, &i);
soit parce qu'il a déjà écrit dans la mémoire à l'endroit de la chaîne de formatage.
Dans ce cas, il peut faire :
int i;
printf("%c %d %n %n %n %n", c, x);
Avec les %n
surnuméraires, il écrit dans le contexte de la fonction appelante ou au delà dans la pile.
Conseils :
- éviter
gets
,strcat
,strcpy
,sprintf
- utiliser
printf("%s", string)
au lieu deprintf(string)
! - faire du fuzzing, c'est-à-dire du test avec des entrées aléatoires, à compris tester les fonctions accessibles à l'utilisateur par exemple avec une chaîne de caractères et un entier sensé être sa longueur, mais différente de sa longueur, etc.
Race conditions
Une race condition ou condition de course désigne l'examen d'un contexte puis la prise de la décision en fonction de cet examen. Par exemple : je regarde si j'ai les droits sur un fichier, si oui, j'écris dedans.
Le problème est que le contexte peutchanger entre l'examen et le moment où on agit. Ce problème arrive souvent avec plusieurs threads. Mais il arrive aussi en séquentiel, avec des fichiers notamment.
if(access(filename, W_OK) == 0) {
if((fd = open(filename, O_WRONLY)) == NULL) {
perror(filename);
return 0;
}
/** écrire dans le fichier */
}
Débordements sur les nombres
longueur = ...;//qui dépend de l'utilisateur
char* buf = (char*) malloc(longueur*2);
if(buf == NULL) return -1;
...
Quel est le problème du code ci-dessus ?
longueur * 2
ne tiennent pas dans un size_t
.
Supposons que size_t
soit un unsigned char
, dans ce cas, longueur = 129
, que se passe-t-il ?
longueur * 2
vaut 2.
Résultat : on écrit en dehors de la zone allouée, et catastrophe. Avec OpenSSH <=3.3, un problème de ce type permettait à un attaquant d'être administrateur.
Remarque : calloc
permet d'allouer n objets de taille m, et ça vérifie que (m \times n) ne déborde pas.
int nombre;
if(nombre < 1000) {
char* buf = malloc(nombre * sizeof(char));
...
}
Est-ce que vous voyez un problème ?
nombre
peut être négatif. On convertit en size_t
non signé, on allouera souvent une place très grande. On peut alors écrire un très gros fichier, ou envoyer plein de données sur Internet...
Bibliographie
Programmation avancée en C. Sébastien Varette et Nicolas Bernard.