Qu'est ce qu'un SUID Script ? C'est un script que vous
souhaiteriez pouvoir exécuter en temps que simple utilisateur
mais qui ferait des choses uniquement permises au root.
Voyons si
il suffit de comprendre ce que signifie un suidbit...
Imaginons que vous soyez root vous voulez permettre à un simple utilisateur de lire le contenu de votre répertoire personnel. Vous écrivez donc un script qui permet d'afficher le contenu de votre répertoire personnel:
[root@rch17 test_TD1]# emacs voir_rep_root &
et on tappe:
#!/bin/sh echo "Contenu du répertoire de" `whoami` # ou echo "Contenu du répertoire de" $(whoami) ls -a /root
verifier la création du fichier:
[root@rch17 test_TD1]# ls -altr voir_rep_root -rw-r--r-- 1 root root 110 nov 19 11:49 voir_rep_root
vous le rendez suid root ( vous pourriez le placez dans /usr/bin pour que tous les utilisateurs puissent l'exécuter en tant que vous !) :
[root@rch17 test_TD1]# chmod 4755 voir_rep_root [root@rch17 test_TD1]$ ls -altr voir_rep_root -rwsr-xr-x 1 root root 110 nov 19 11:49 voir_rep_root*
Vous testez que ça marche pour vous puis en temps que
utilisateur normal (l'utilisateur toto par exemple)
[root@rch17 test_TD1]# ./voir_rep_root Contenu du répertoire de root . .gtkrc-1.2-gnome2 .. .ICEauthority alsa-driver-0.9.8 .ispell_english . . . [root@rch17 test_TD1]# su toto [toto@rch17 test_TD1]$ ./voir_rep_root Contenu du répertoire de toto ls: /root: Permission denied
Vous constatez le problème ... Votre script est exécutable, appartient au root, a le SUID bit à 1 mais le système n'en tient pas compte.
Vous vous dites alors : "Je vais l'avoir en finesse...".
Si sur les programmes ça marche, on est tentés de
se dire qu'on va écrire un programme qui appelle le script.
Allons y, créons lanceur_de_script_V1.c :
[root@rch17 test_TD1]# emacs lanceur_de_script_V1.c &
on tape:
int main(int argc, char *argv[]) { system("./voir_rep_root"); }
On le compile, on le rend exécutable par tout le monde, on met le SUID bit à 1 et on refait le test de tout à l'heure.
[root@rch17 test_TD1]# gcc -o lanceur_de_script lanceur_de_script_V1.c [root@rch17 test_TD1]# chmod 4111 lanceur_de_script [root@rch17 test_TD1]# ls -altr lanceur_de_script ---s--x--x 1 root root 11424 nov 19 11:56 lanceur_de_script* [root@rch17 test_TD1]#./lanceur_de_script Contenu du répertoire de root . .gtkrc-1.2-gnome2 .. .ICEauthority alsa-driver-0.9.8 .ispell_english . . . [root@rch17 test_TD1]# su toto [toto@rch17 test_TD1]$ ./lanceur_de_script Contenu du répertoire de toto ls: /root: Permission denied
Pour suivre le raisonnement, ajoutez dans le programme une ligne
:
printf ("UID %d - EUID %d\n", getuid(),
geteuid());
comme ci-dessous :
[root@rch17 test_TD1]# emacs lanceur_de_script_V2.c
on tappe:
int main(int argc, char *argv[]) { system("./voir_rep_root"); printf ("UID %d - EUID %d\n", getuid(), geteuid()); }
Cela vous donne un début de réponse lors de l'exécution :
[root@rch17 test_TD1]# gcc -o lanceur_de_script lanceur_de_script_V2.c [root@rch17 test_TD1]# chmod 4111 lanceur_de_script [root@rch17 test_TD1]# su toto [toto@rch17 test_TD1]$ ./l lanceur_de_script lanceur_de_script2 [toto@rch17 test_TD1]$ ./lanceur_de_script Contenu du répertoire de toto ls: /root: Permission denied UID 1019 - EUID 0
Oui, la réponse se situe dans la première ligne de sortie du programme. L'EUID du programme, celle fixée par le SUID bit est bien 0 (root) mais l'uid (celle utilisée pour l'appel du script) est 1019.
A quoi correspond 1019 dans le fichier /etc/passwd ?
Dans cet exemple c'est l'utilisateur toto. D'où un problème dans le comportement attendu ! En fait le script est appelé avec l'UID et non l'EUID. Il faut donc faire un pas supplémentaire et dire au programme de lancer le script en temps que root, c'est à dire faire en sorte que l'UID devienne l'EUID.
Cela n'est rendu possible que parce que le programme est SUID root. Un programme lancé par le root peut prendre l'UID de n'importe qui, par contre un programme lancé par un utilisateur classique ne peut prendre comme UID que son EUID. C'est à dire l'UID de l'utilisateur qui l'a créé, sous réserve qu'il ait placé le SUID bit à 1.
Ce changement se fait grâce à la fonction
setreuid. Les fonctions seteuid, setuid et
setreuid servent à manipuler les UID et EUID d'un
programme. setuid modifie prend un paramètre qu'il
affecte à l'UID et l'EUID. seteuid prend un paramètre
qu'il affecte à l'EUID. setreuid en prend deux,
affect le premier à l'UID et le second à l'EUID. Si un
de ces paramètres vaut -1 la valeur correspondante n'est pas
changée.
DONC setuid(ID) est équivalent à
setreuid(ID, ID) et seteuid(ID) est équivalent
à setreuid(-1, ID).
Voici l'illustration de cette partie dans le fichier lanceur_de_script_V3.c :
#include <sys/types.h> int main(int argc, char *argv[]) { uid_t uid, euid; uid=getuid(); euid=geteuid(); printf ("UID %d - EUID %d\n", uid, euid); setreuid (euid, euid); uid=getuid(); euid=geteuid(); printf ("UID %d - EUID %d\n", uid, euid); system("./voir_rep_root"); }
Une fois le code mis à jour :
[root@rch17 test_TD1]# gcc -o lanceur_de_script lanceur_de_script_V3.c [root@rch17 test_TD1]# ./lanceur_de_script UID 0 - EUID 0 UID 0 - EUID 0 Contenu du répertoire de root . .gtkrc-1.2-gnome2 .. .ICEauthority . . . [root@rch17 test_TD1]# chmod 4111 lanceur_de_script [root@rch17 test_TD1]# su toto [toto@rch17 test_TD1]$ ./lanceur_de_script UID 1019 - EUID 0 UID 0 - EUID 0 Contenu du répertoire de root . .gtkrc-1.2-gnome2 .. .ICEauthority alsa-driver-0.9.8 .ispell_english . . .
Vous voyez qu'après l'appel à setreuid, nous sommes non seulement UID root mais aussi EUID root et que de par le fait, tous les scripts que nous appelons s'exécutent avec l'identité root... Donc, ça marche !
Ce n'est pas la fin car vous vous rendez sûrement compte
à présent que le script n'a pas besoin d'être
SUID root pour que celà fonctionne...
[root@rch17 test_TD1]# chmod 711 voir_rep_root [root@rch17 test_TD1]# ls -l voir_rep_root -rwx--x--x 1 root root 110 nov 19 11:49 voir_rep_root* [root@rch17 test_TD1]# su toto [toto@rch17 test_TD1]$ ./lanceur_de_script UID 1019 - EUID 0 UID 0 - EUID 0 Contenu du répertoire de root . .gtkrc-1.2-gnome2 .. .ICEauthority alsa-driver-0.9.8 .ispell_english . . .
Imaginons, un instant que l'on remplace l'appel statique au
script par un appel à un script passé en paramètre...
Voici l'illustration de cette partie dans le fichier lanceur_de_script_V4.c :
#include <unistd.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> int main(int argc, char *argv[]) { char test[100]; uid_t uid, euid; uid=getuid(); euid=geteuid(); printf ("UID %d - EUID %d\n", uid, euid); setreuid (euid, euid); uid=getuid(); euid=geteuid(); printf ("UID %d - EUID %d\n", uid, euid); strcpy(test,"" ); printf(test); strcat(test,argv[1]); printf(test); system(test); }
alors:
[root@rch17 test_TD1]# gcc -o lanceur_de_script lanceur_de_script_V4.c [root@rch17 test_TD1]# chmod 4111 lanceur_de_script [root@rch17 test_TD1]# su toto [toto@rch17 test_TD1]$ ./lanceur_de_script "/bin/bash" UID 1019 - EUID 0 UID 0 - EUID 0 [root@rch17 test_TD1]#
Ca devient pratique mais aussi trop dangereux, n'importe quel script pouvant être exécuté en temps que root...
Ca marche ! Mais c'est dangereux... D'autre part, le setreuid
vous permet certes de gagner l'accès au privilèges du
root dans un programme SUID mais il vous permet aussi de les
abandonner lorsque vous n'en avez plus besoin.
Celà demande
des exemples, allons y, créons le fichier
createur_de_fichier.c en temps que root :
#include <unistd.h> #include <errno.h> #include <stdio.h> #include <sys/types.h> extern char **environ; extern int errno; int main (int argc, char **argv) { FILE * fd; uid_t uid, euid; uid=getuid(); euid=geteuid(); printf ("UID %d - EUID %d\n", uid, euid); if (!(fd = fopen("/root/test", "w"))) printf ("Je n'ai pas pu créer le fichier /root/test avant setreuid\n"); else { printf ("J'ai pu créer le fichier /root/test avant setreuid\n"); fclose(fd); } setreuid (euid, uid); uid=getuid(); euid=geteuid(); printf ("UID %d - EUID %d\n", uid, euid); if (!(fd = fopen("/root/test", "w"))) printf ("Je n'ai pas pu créer le fichier /root/test après setreuid\n"); else { printf ("J'ai pu créer le fichier /root/test après setreuid\n"); fclose(fd); } }
Dans le fichier ci-dessus on tente de créer un fichier en c dans un programme SUID puis après avoir inversé l'uid et l'euid. On se rend alors compte que contrairement au cas précédent, c'est l'uid qui compte lors des actions du programme.
[root@rch17 test_TD1]# gcc -o createur_fichier createur_fichier.c [root@rch17 test_TD1]# chmod 4111 createur_fichier [root@rch17 test_TD1]# su toto [toto@rch17 test_TD1]$ ./createur_fichier UID 1019 - EUID 0 J'ai pu créer le fichier /root/test avant setreuid UID 0 - EUID 1019 Je n'ai pas pu créer le fichier /root/test après setreuid
Pour ce qui est du danger, il suffit de créer un programme en c permettant d'exécuter un script passé en paramètre mais qui vérifie si ce script fait partie d'une liste de scripts autorisés stockée chez le root, dans le répertoire /root/.authoscripts/, dans le fichier liste. D'autre part, les scripts sont cherchés uniquement dans ce répertoire, ainsi, il n'y a que le root qui puisse en ajouter. Il y a quelques instructions à taper :
[root@rch17 /root]# mkdir .authoscripts [root@rch17 /root]# cd .authoscripts/ [root@rch17 .authoscripts]# touch liste [root@rch17 .authoscripts]# cat >> liste voir_rep_root < Ctrl+D > [root@rch17 .authoscripts]# [root@rch17 .authoscripts]# mv voir_rep_root ./
Créons maintenant la prise en charge des SUID Scripts. On
ajoute aux contraintes qu'il doit être possible de passer des
paramètres aux scripts.
Tout cela nous donne ce code ci :
#include <unistd.h> #include <errno.h> #include <stdio.h> #include <sys/types.h> #include <string.h> extern char **environ; extern int errno; int main (int argc, char **argv) { FILE * fd; uid_t uid, euid; int isOK = 0; char tmpBuff[256]; if (argc<2) { fprintf(stderr, "USAGE : suscript nom_du_script param_1 param_2 ...\n"); return 1; } if (!(fd=fopen("/root/.authoscripts/liste", "r"))) { fprintf(stderr, "ERREUR : Impossible d'ouvrir le fichier liste dans\ /root/.authoscripts.\n"); return 2; } while (!feof(fd)) { fscanf(fd, "%s", tmpBuff); if (!strcmp(argv[1], tmpBuff)) { isOK++; break; } } fclose (fd); if (!isOK) { fprintf(stderr, "ERREUR : %s n'est pas un script autorisé...\n", argv[1]); return 3; } uid=getuid(); euid=geteuid(); setreuid (euid, euid); sprintf (tmpBuff, "/root/.authoscripts/%s", argv[1]); execve (tmpBuff, &argv[1], environ); printf ("Erreur : %d - %s\n", errno, strerror(errno)); return errno; }
Que l'on compile et range ainsi :
[root@rch17 /root]# gcc -o suscript suscript.c [root@rch17 /root]# chmod 4111 suscript [root@rch17 /root]# mv suscript /tmp
Dont on vérifie le bon fonctionnement grâce à la série de commandes qui suit.
[root@rch17 /root]# cd .authoscripts/ [root@rch17 .authoscripts]# cat > script_test #!/bin/sh echo id: `whoami` echo "Params :" $* < Ctrl+D > [root@rch17 .authoscripts]# chmod a+x script_test [root@rch17 .authoscripts]# echo "script_test" >> liste [root@rch17 .authoscripts]# su toto [toto@rch17 .authoscripts]$ cd [toto@rch17 toto]$ suscript script_test 1 2 3 4 5 6 7 8 9 0 id: root Params : 1 2 3 4 5 6 7 8 9 0
Exemple: Ce fichier est capable de lire le contenu d'un fichier texte.
/* fichier affichefich.c */ #include <stdio.h> int main( int argc, char *argv[] ) { FILE *f; char c; /* 1- Ouvre le fichier specifie en argument (argv[1]) en lecture */ f = fopen( argv[1], "r" ); /* 2- recopie caractere par caractere son contenu sur stdout */ do { c = fgetc(f); if (c != EOF) fputc(c, stdout); } while (c != EOF); }
[root@rch17 test_TD1]# g++ -o lire_fichier lire_fichier.cpp [root@rch17 test_TD1]# echo cache > mon_test [root@rch17 test_TD1]# ls -al mon_test -rw-r--r-- 1 root root 6 nov 19 13:57 mon_test [root@rch17 test_TD1]# chmod 700 mon_test [root@rch17 test_TD1]# ls -al mon_test -rwx------ 1 root root 6 nov 19 13:57 mon_test* [root@rch17 test_TD1]# chmod 4111 lire_fichier [root@rch17 test_TD1]# su toto [toto@rch17 test_TD1]$ ./lire_fichier mon_test cache [toto@rch17 test_TD1]$ more test test: Permission denied
c'est plus sûr!
man execve et entre autres, la section NOTES :
NOTES SUID and SGID processes can not be ptrace()d SUID or SGID. A maximum line length of 127 characters is allowed for the first line in a #! executable shell script. Linux ignores the SUID and SGID bits on scripts.
man getuid
man setuid
man setreuid
man getlogin
man cuserid
man chmod