« SUID scripts » : différence entre les versions
(balises code) |
m (Lea a déplacé la page Dev-suid scripts vers SUID scripts) |
(Aucune différence)
|
Dernière version du 1 décembre 2023 à 18:47
SUID Scripts
Afin d'éviter une polémique, je dis tout de suite que la réponse apportée ci-dessous m'avait été inspirée par la lecture de l'article "Eviter les failles dès le développement de vos applications" paru dans le Linuxmagazine-france de Décembre 2000. Ceci dit, le problème est traité ici dans une optique différente qui est de permettre à un administrateur de permettre aux utilisateurs de lancer quelques scripts choisis en temps que root.
Introduction
Qu'est ce que j'appelle 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.
J'en vois qui sourient en me prenant pour un débile se jeter sur leur shell pour me prouver que je suis un tocard qui ne connait pas chmod 4755. Lisez donc encore quelques lignes avant de m'envoyer un mail d'insultes.
Un échec
Voyons si cet article s'adresse à vous !
Imaginons que vous soyez root vous voulez permettre à un simple utilisateur de lire le contenu de votre répertoire personnel, dans un souci de transparence, pour prouver qu'ils n'y trouveront pas de photos pornographiques. Vous écrivez donc un script qui permet d'afficher le contenu de votre répertoire personnel ( je présuppose chez vous des bases de shell ;-) ) :
#!/bin/sh echo "Contenu du répertoire de" `whoami` # ou echo "Contenu du répertoire de" $(whoami) ls -a /root< Ctrl+D >
et vous le rendez suid root et vous le placez dans /usr/bin pour que tous les utilisateurs puissent l'exécuter en temps que vous ! :
(Les plus assidus d'entre vous constaterons que mon PC ne s'appelle plus Rooty mais là n'est pas la question)
Vous testez que ça marche pour vous puis en tant qu'utilisateur normal (l'utilisateur xavier par exemple)
Contenu du répertoire de root . .bash_history .cshrc .toprc Mail .. .bash_logout .mysql_history .vimrc .Xauthority .bash_profile .parsecrc .wmrc .Xdefaults .bashrc .tcshrc .zshrc [root@ZAZOU /root]# su xavier [xavier@ZAZOU /root]$ voir_rep_root Contenu du répertoire de xavierls: /root: Permission non accordée
Vous avez compris le problème ça y est ? Votre script est exécutable, appartient au root, a le SUID bit à 1 mais n'en tient pas compte.
- Scènette de fin de partie
- "- La solution c'est chmod 4755 /bin/bash
- Qui a dit ça ? Bill ? A la porte, tout de suite !"
Bill ? Porte ? Je vous laisse méditer là dessus et passe à l'étape suivante.
En C, ça ne marche pas non plus (au début) !
Vous vous dites alors : "Je suis très intelligent, je vais l'avoir en finesse...". Hé hé hé ! Je rigole parce que c'est ce que je me suis dit moi aussi ...
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.c :
#include <errno.h> #include <stdio.h>
extern char **environ; extern int errno;
int main (int argc, char **argv) { execve("/usr/bin/voir_rep_root", NULL, environ); printf ("Error : %d\n", errno); return errno; }
( Parenthèse : A ceux qui seraient tentés de dire : "Oui mais pourquoi on met return errno ?", je réponds d'aller lire le man execve où ils pourront constater que si execve ne rencontre pas d'erreur, alors il ne renvoie rien puisque l'image du programme est TOTALEMENT remplacée par celle du programme appelé (ce programme étant l'interpréteur dans le cas d'un script). )
On est content d'avoir fait ce beau programme. Alors on le compile, on le rend exécutable par tout le monde, on met le SUID bit à 1, on le déplace dans /usr/bin et on refait le test de tout à l'heure.
[root@ZAZOU /root]# chmod 4111 lanceur_de_script [root@ZAZOU /root]# mv -f lanceur_de_script /usr/bin/ [root@ZAZOU /root]# lanceur_de_script Contenu du répertoire de root . .bash_history .cshrc .toprc Mail .. .bash_logout .mysql_history .vimrc lanceur_de_script.c .Xauthority .bash_profile .parsecrc .wmrc .Xdefaults .bashrc .tcshrc .zshrc [root@ZAZOU /root]# su xavier [xavier@ZAZOU /root]$ lanceur_de_script Contenu du répertoire de xavierls: /root: Permission non accordée
Le début de la compréhension
Oui, pour comprendre, rendez vous à la fin de l'article ;-) ... Pour suivre le raisonnement, ajoutez dans le programme une ligne :
comme ci-dessous :
#include <errno.h> #include <stdio.h> #include <sys/types.h>
extern char **environ; extern int errno;
int main (int argc, char **argv) { printf ("UID %d - EUID %d\n", getuid(), geteuid()); execve("/usr/bin/voir_rep_root", NULL, environ); printf ("Error : %d\n", errno); return errno;}
Celà vous donne un début de réponse lors de l'exécution (non ?) :
[root@ZAZOU /root]# mv -f lanceur_de_script /usr/bin/ [root@ZAZOU /root]# su xavier [xavier@ZAZOU /root]$ lanceur_de_script UID 501 - EUID 0 Contenu du répertoire de xavierls: /root: Permission non accordée
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 501. Or, 501 correspond d'après mon fichier
à l'utilisateur xavier. 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.
Ça marche mais c'est dangereux
Celà 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.
Bref ! Ce changement se fait grâce à la fonction
. Les fonctions
,
et
servent à manipuler les UID et EUID d'un programme.
modifie prend un paramètre qu'il affecte à l'UID et l'EUID.
prend un paramètre qu'il affecte à l'EUID.
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
est équivalent à
et
est équivalent à
.
Voici l'illustration de cette partie dans le fichier lanceur_de_script_2.c :
#include <errno.h> #include <stdio.h> #include <sys/types.h>
extern char **environ; extern int errno;
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); execve("/usr/bin/voir_rep_root", NULL, environ); printf ("Error : %d\n", errno); return errno;}
Une fois le code mis à jour :
[root@ZAZOU /root]# chmod 4111 lanceur_de_script_2 [root@ZAZOU /root]# mv lanceur_de_script_2 /usr/bin/ [root@ZAZOU /root]# su xavier [xavier@ZAZOU /root]$ lanceur_de_script_2 UID 501 - EUID 0 UID 0 - EUID 0 Contenu du répertoire de root . .bash_history .cshrc .toprc Mail .. .bash_logout .mysql_history .vimrc lanceur_de_script.c .Xauthority .bash_profile .parsecrc .wmrc lanceur_de_script_2.c.Xdefaults .bashrc .tcshrc .zshrc
Vous voyez qu'après l'appel à
, 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 ! Fin de l'article.
Mais non, ce n'est pas la fin de l'article car vous vous rendez sûrement compte à ce niveau que le script n'a pas besoin d'être SUID root pour que celà fonctionne...
Imaginons, un instant que l'on remplace l'appel statique au script par un appel à un script passé en paramètre... Ca devient super pratique mais aussi super dangereux, n'importe quel script pouvant être exécuté en temps que root...
Nicking ze danger !
Ouaip, ça marche ! Mais c'est dangeureux et cet article est là pour vous en faire prendre conscience... D'autre part, un autre point à comprendre est que
vous permet certes de gagner l'accés au privilèges du root dans un programme SUID mais qu'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 pour prendre conscience du côté éphémère de la toute puissance :
#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@ZAZOU /root]# chmod 4111 createur_de_fichier [root@ZAZOU /root]# mv -f createur_de_fichier /usr/bin/ [root@ZAZOU /root]# su xavier [xavier@ZAZOU /root]$ createur_de_fichier UID 501 - EUID 0 J'ai pu créer le fichier /root/test avant setreuid UID 0 - EUID 501 Je n'ai pas pu créer le fichier /root/test après setreuid[xavier@ZAZOU /root]$
Pour ce qui est du danger, il suffit de créer un programme en c permmettant 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
, dans le fichier
. 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@ZAZOU /root]# cd .authoscripts/ [root@ZAZOU .authoscripts]# touch liste [root@ZAZOU .authoscripts]# cat >> liste voir_rep_root < Ctrl+D > [root@ZAZOU .authoscripts]#[root@ZAZOU .authoscripts]# mv /usr/bin/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 celà nous donne ce code ci :
#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@ZAZOU /root]# chmod 4111 suscript[root@ZAZOU /root]# mv suscript /usr/bin
Dont on vérifie le bon foncionnement grâce à la série de commandes qui suit.
[root@ZAZOU .authoscripts]# cat > script_test #!/bin/sh echo id: `whoami` echo "Params :" $* < Ctrl+D > [root@ZAZOU .authoscripts]# chmod a+x script_test [root@ZAZOU .authoscripts]# su xavier [xavier@ZAZOU .authoscripts]$ cd [xavier@ZAZOU xavier]$ suscript script_test 1 2 3 4 5 6 7 8 9 0 id: rootParams : 1 2 3 4 5 6 7 8 9 0
En savoir plus
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.
Vous avez compris le pourquoi de cet article ?
Tshaw
C'est tout pour cette fois !
N'hésitez pas à m'envoyer vos commentaires par mail, il reste sûrement des améliorations à faire.
Retrouvez l'article remis en forme, les sources, archives et binaires sur mon site perso..., http://perso.club-internet.fr/xgarreau/
Vous pouvez également me contacter dans le forum Développement de Léa, je le regarde (très) souvent.
A bientôt,
Xavier GARREAU
Ingénieur de recherche PRIM'TIME TECHNOLOGY : http://www.prim-time.com/
Membre fondateur et président (2002-20??) du ROCHELUG : http://lug.larochelle.tuxfamily.org/
Pigiste ;-) (Léa-Linux et LinuxMag-France)
@ Retour à la rubrique Développement
Copyright
Copyright © 16/02/2002, Xavier GARREAU
![]() ![]() ![]() ![]() |
Ce document est publié sous licence Creative Commons Attribution, Partage à l'identique, Contexte non commercial 2.0 : http://creativecommons.org/licenses/by-nc-sa/2.0/fr/ |