« SUID scripts » : différence entre les versions
m (→Un échec) |
m (Lea a déplacé la page Dev-suid scripts vers SUID scripts) |
||
(2 versions intermédiaires par un autre utilisateur non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
[[ | [[Catégorie:Développement]] | ||
= SUID Scripts = | = SUID Scripts = | ||
Ligne 7 : | Ligne 8 : | ||
== Introduction == | == 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. <br /> 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 < | 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. <br /> 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 <tt>chmod 4755</tt>. Lisez donc encore quelques lignes avant de m'envoyer un mail d'insultes. | ||
== Un échec == | == Un échec == | ||
Ligne 13 : | Ligne 14 : | ||
Voyons si cet article s'adresse à vous !<br /> 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 ;-) ) : | Voyons si cet article s'adresse à vous !<br /> 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 ;-) ) : | ||
<code> [root@ZAZOU /root]# cat > voir_rep_root | <div class="code"> [root@ZAZOU /root]# cat > voir_rep_root | ||
#!/bin/sh | #!/bin/sh | ||
echo "Contenu du répertoire de" `whoami` | echo "Contenu du répertoire de" `whoami` | ||
# ou echo "Contenu du répertoire de" $(whoami) | # ou echo "Contenu du répertoire de" $(whoami) | ||
ls -a /root | ls -a /root | ||
< Ctrl+D ></ | < Ctrl+D ></div> | ||
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 ! : | 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 ! : | ||
<code> [root@ZAZOU /root]# chmod 4755 voir_rep_root | <div class="code"> [root@ZAZOU /root]# chmod 4755 voir_rep_root | ||
[root@ZAZOU /root]# mv voir_rep_root /usr/bin/</ | [root@ZAZOU /root]# mv voir_rep_root /usr/bin/</div> | ||
(Les plus assidus d'entre vous constaterons que mon PC ne s'appelle plus Rooty mais là n'est pas la question) <br /> Vous testez que ça marche pour vous puis en tant qu'utilisateur normal (l'utilisateur xavier par exemple) | (Les plus assidus d'entre vous constaterons que mon PC ne s'appelle plus Rooty mais là n'est pas la question) <br /> Vous testez que ça marche pour vous puis en tant qu'utilisateur normal (l'utilisateur xavier par exemple) | ||
<code> [root@ZAZOU /root]# voir_rep_root | <div class="code"> [root@ZAZOU /root]# voir_rep_root | ||
Contenu du répertoire de root | Contenu du répertoire de root | ||
. .bash_history .cshrc .toprc Mail | . .bash_history .cshrc .toprc Mail | ||
Ligne 36 : | Ligne 37 : | ||
[xavier@ZAZOU /root]$ voir_rep_root | [xavier@ZAZOU /root]$ voir_rep_root | ||
Contenu du répertoire de xavier | Contenu du répertoire de xavier | ||
ls: /root: Permission non accordée</ | ls: /root: Permission non accordée</div> | ||
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. | 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''' | ; '''Scènette de fin de partie''' | ||
: "''- La solution c'est <code>chmod 4755 /bin/bash</ | : "''- La solution c'est <div class="code">chmod 4755 /bin/bash</div><br /> - Qui a dit ça ? Bill ? A la porte, tout de suite !"''<br /> | ||
Bill ? Porte ? Je vous laisse méditer là dessus et passe à l'étape suivante. | Bill ? Porte ? Je vous laisse méditer là dessus et passe à l'étape suivante. | ||
Ligne 49 : | Ligne 50 : | ||
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 ...<br /> 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 : | 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 ...<br /> 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 : | ||
<code> #include <unistd.h> | <div class="code"> #include <unistd.h> | ||
#include <errno.h> | #include <errno.h> | ||
#include <stdio.h> | #include <stdio.h> | ||
Ligne 61 : | Ligne 62 : | ||
return errno; | return errno; | ||
} | } | ||
</ | </div> | ||
( 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). )<br /> 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. | ( 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). )<br /> 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. | ||
<code> [root@ZAZOU /root]# gcc -o lanceur_de_script lanceur_de_script.c | <div class="code"> [root@ZAZOU /root]# gcc -o lanceur_de_script lanceur_de_script.c | ||
[root@ZAZOU /root]# chmod 4111 lanceur_de_script | [root@ZAZOU /root]# chmod 4111 lanceur_de_script | ||
[root@ZAZOU /root]# mv -f lanceur_de_script /usr/bin/ | [root@ZAZOU /root]# mv -f lanceur_de_script /usr/bin/ | ||
Ligne 77 : | Ligne 78 : | ||
[xavier@ZAZOU /root]$ lanceur_de_script | [xavier@ZAZOU /root]$ lanceur_de_script | ||
Contenu du répertoire de xavier | Contenu du répertoire de xavier | ||
ls: /root: Permission non accordée</ | ls: /root: Permission non accordée</div> | ||
== Le début de la compréhension == | == 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 :<br /><code>printf ("UID %d - EUID %d\n", getuid(), geteuid());</ | Oui, pour comprendre, rendez vous à la fin de l'article ;-) ... Pour suivre le raisonnement, ajoutez dans le programme une ligne :<br /><div class="code">printf ("UID %d - EUID %d\n", getuid(), geteuid());</div> comme ci-dessous : | ||
<code> #include <unistd.h> | <div class="code"> #include <unistd.h> | ||
#include <errno.h> | #include <errno.h> | ||
#include <stdio.h> | #include <stdio.h> | ||
Ligne 96 : | Ligne 97 : | ||
printf ("Error : %d\n", errno); | printf ("Error : %d\n", errno); | ||
return errno; | return errno; | ||
}</ | }</div> | ||
Celà vous donne un début de réponse lors de l'exécution (non ?) : | Celà vous donne un début de réponse lors de l'exécution (non ?) : | ||
<code> [root@ZAZOU /root]# chmod 4111 lanceur_de_script | <div class="code"> [root@ZAZOU /root]# chmod 4111 lanceur_de_script | ||
[root@ZAZOU /root]# mv -f lanceur_de_script /usr/bin/ | [root@ZAZOU /root]# mv -f lanceur_de_script /usr/bin/ | ||
[root@ZAZOU /root]# su xavier | [root@ZAZOU /root]# su xavier | ||
Ligne 106 : | Ligne 107 : | ||
UID 501 - EUID 0 | UID 501 - EUID 0 | ||
Contenu du répertoire de xavier | Contenu du répertoire de xavier | ||
ls: /root: Permission non accordée</ | ls: /root: Permission non accordée</div> | ||
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 <code>/etc/passwd</ | 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 <div class="code">/etc/passwd</div> à 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 == | == Ç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.<br /> Bref ! Ce changement se fait grâce à la fonction <code>setreuid</ | 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.<br /> Bref ! Ce changement se fait grâce à la fonction <div class="code">setreuid</div>. Les fonctions <div class="code">seteuid</div>, <div class="code">setuid</div> et <div class="code">setreuid</div> servent à manipuler les UID et EUID d'un programme. <div class="code">setuid</div> modifie prend un paramètre qu'il affecte à l'UID et l'EUID. <div class="code">seteuid</div> prend un paramètre qu'il affecte à l'EUID. <div class="code">setreuid</div> 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.<br />'''DONC''' <div class="code">setuid(ID)</div> est équivalent à <div class="code">setreuid(ID, ID)</div> et <div class="code">seteuid(ID)</div> est équivalent à <div class="code">setreuid(-1, ID)</div>. | ||
Voici l'illustration de cette partie dans le fichier lanceur_de_script_2.c : | Voici l'illustration de cette partie dans le fichier lanceur_de_script_2.c : | ||
<code> #include <unistd.h> | <div class="code"> #include <unistd.h> | ||
#include <errno.h> | #include <errno.h> | ||
#include <stdio.h> | #include <stdio.h> | ||
Ligne 136 : | Ligne 137 : | ||
printf ("Error : %d\n", errno); | printf ("Error : %d\n", errno); | ||
return errno; | return errno; | ||
}</ | }</div> | ||
Une fois le code mis à jour : | Une fois le code mis à jour : | ||
<code> [root@ZAZOU /root]# gcc -o lanceur_de_script_2 lanceur_de_script_2.c | <div class="code"> [root@ZAZOU /root]# gcc -o lanceur_de_script_2 lanceur_de_script_2.c | ||
[root@ZAZOU /root]# chmod 4111 lanceur_de_script_2 | [root@ZAZOU /root]# chmod 4111 lanceur_de_script_2 | ||
[root@ZAZOU /root]# mv lanceur_de_script_2 /usr/bin/ | [root@ZAZOU /root]# mv lanceur_de_script_2 /usr/bin/ | ||
Ligne 151 : | Ligne 152 : | ||
.. .bash_logout .mysql_history .vimrc lanceur_de_script.c | .. .bash_logout .mysql_history .vimrc lanceur_de_script.c | ||
.Xauthority .bash_profile .parsecrc .wmrc lanceur_de_script_2.c | .Xauthority .bash_profile .parsecrc .wmrc lanceur_de_script_2.c | ||
.Xdefaults .bashrc .tcshrc .zshrc</ | .Xdefaults .bashrc .tcshrc .zshrc</div> | ||
Vous voyez qu'après l'appel à <code>setreuid</ | Vous voyez qu'après l'appel à <div class="code">setreuid</div>, 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.<br /> 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...<br /> 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 ! == | == 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 <code>setreuid</ | 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 <div class="code">setreuid</div> 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.<br /> 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 : | ||
<code> #include <unistd.h> | <div class="code"> #include <unistd.h> | ||
#include <errno.h> | #include <errno.h> | ||
#include <stdio.h> | #include <stdio.h> | ||
Ligne 190 : | Ligne 191 : | ||
fclose(fd); | fclose(fd); | ||
} | } | ||
}</ | }</div> | ||
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. | 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. | ||
<code> [root@ZAZOU /root]# gcc -o createur_de_fichier createur_de_fichier.c | <div class="code"> [root@ZAZOU /root]# gcc -o createur_de_fichier createur_de_fichier.c | ||
[root@ZAZOU /root]# chmod 4111 createur_de_fichier | [root@ZAZOU /root]# chmod 4111 createur_de_fichier | ||
[root@ZAZOU /root]# mv -f createur_de_fichier /usr/bin/ | [root@ZAZOU /root]# mv -f createur_de_fichier /usr/bin/ | ||
Ligne 203 : | Ligne 204 : | ||
UID 0 - EUID 501 | UID 0 - EUID 501 | ||
Je n'ai pas pu créer le fichier /root/test après setreuid | Je n'ai pas pu créer le fichier /root/test après setreuid | ||
[xavier@ZAZOU /root]$</ | [xavier@ZAZOU /root]$</div> | ||
'''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 <code>/root/.authoscripts/</ | '''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 <div class="code">/root/.authoscripts/</div>, dans le fichier <div class="code">liste</div>. 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 : | ||
<code> [root@ZAZOU /root]# mkdir .authoscripts | <div class="code"> [root@ZAZOU /root]# mkdir .authoscripts | ||
[root@ZAZOU /root]# cd .authoscripts/ | [root@ZAZOU /root]# cd .authoscripts/ | ||
[root@ZAZOU .authoscripts]# touch liste | [root@ZAZOU .authoscripts]# touch liste | ||
Ligne 214 : | Ligne 215 : | ||
< Ctrl+D > | < Ctrl+D > | ||
[root@ZAZOU .authoscripts]# | [root@ZAZOU .authoscripts]# | ||
[root@ZAZOU .authoscripts]# mv /usr/bin/voir_rep_root ./</ | [root@ZAZOU .authoscripts]# mv /usr/bin/voir_rep_root ./</div> | ||
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.<br /> Tout celà nous donne ce code ci : | 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.<br /> Tout celà nous donne ce code ci : | ||
<code> #include <unistd.h> | <div class="code"> #include <unistd.h> | ||
#include <errno.h> | #include <errno.h> | ||
#include <stdio.h> | #include <stdio.h> | ||
Ligne 261 : | Ligne 262 : | ||
printf ("Erreur : %d - %s\n", errno, strerror(errno)); | printf ("Erreur : %d - %s\n", errno, strerror(errno)); | ||
return errno; | return errno; | ||
}</ | }</div> | ||
Que l'on compile et range ainsi : | Que l'on compile et range ainsi : | ||
<code> [root@ZAZOU /root]# gcc -o suscript suscript.c | <div class="code"> [root@ZAZOU /root]# gcc -o suscript suscript.c | ||
[root@ZAZOU /root]# chmod 4111 suscript | [root@ZAZOU /root]# chmod 4111 suscript | ||
[root@ZAZOU /root]# mv suscript /usr/bin</ | [root@ZAZOU /root]# mv suscript /usr/bin</div> | ||
Dont on vérifie le bon foncionnement grâce à la série de commandes qui suit. | Dont on vérifie le bon foncionnement grâce à la série de commandes qui suit. | ||
<code> [root@ZAZOU /root]# cd .authoscripts/ | <div class="code"> [root@ZAZOU /root]# cd .authoscripts/ | ||
[root@ZAZOU .authoscripts]# cat > script_test | [root@ZAZOU .authoscripts]# cat > script_test | ||
#!/bin/sh | #!/bin/sh | ||
Ligne 282 : | Ligne 283 : | ||
[xavier@ZAZOU xavier]$ suscript script_test 1 2 3 4 5 6 7 8 9 0 | [xavier@ZAZOU xavier]$ suscript script_test 1 2 3 4 5 6 7 8 9 0 | ||
id: root | id: root | ||
Params : 1 2 3 4 5 6 7 8 9 0</ | Params : 1 2 3 4 5 6 7 8 9 0</div> | ||
== En savoir plus == | == En savoir plus == | ||
<code>man execve</ | <div class="code">man execve</div> et entre autres, la section NOTES : | ||
NOTES | NOTES | ||
Ligne 298 : | Ligne 299 : | ||
Vous avez compris le pourquoi de cet article ? | Vous avez compris le pourquoi de cet article ? | ||
<code>man getuid</ | <div class="code">man getuid</div> | ||
<code>man setuid</ | <div class="code">man setuid</div> | ||
<code>man setreuid</ | <div class="code">man setreuid</div> | ||
<code>man getlogin</ | <div class="code">man getlogin</div> | ||
<code>man cuserid</ | <div class="code">man cuserid</div> | ||
<code>man chmod</ | <div class="code">man chmod</div> | ||
== Tshaw == | == Tshaw == | ||
C'est tout pour cette fois !<br /> N'hésitez pas à m'envoyer vos commentaires par mail, il reste sûrement des améliorations à faire.<br /> Retrouvez l'article remis en forme, les sources, archives et binaires sur mon site perso..., http://perso.club-internet.fr/xgarreau/<br /> Vous pouvez également me contacter dans le forum Développement de Léa, je le regarde (très) souvent. <br /> A bientôt,<br /> Xavier GARREAU<br /> Ingénieur de recherche PRIM'TIME TECHNOLOGY : http://www.prim-time.com/<br /> Membre fondateur et président (2002-20??) du ROCHELUG : http://lug.larochelle.tuxfamily.org/<br /> Pigiste ;-) (Léa-Linux et LinuxMag-France) | C'est tout pour cette fois !<br /> N'hésitez pas à m'envoyer vos commentaires par mail, il reste sûrement des améliorations à faire.<br /> Retrouvez l'article remis en forme, les sources, archives et binaires sur mon site perso..., http://perso.club-internet.fr/xgarreau/<br /> Vous pouvez également me contacter dans le forum Développement de Léa, je le regarde (très) souvent. <br /> A bientôt,<br /> Xavier GARREAU<br /> Ingénieur de recherche PRIM'TIME TECHNOLOGY : http://www.prim-time.com/<br /> Membre fondateur et président (2002-20??) du ROCHELUG : http://lug.larochelle.tuxfamily.org/<br /> Pigiste ;-) (Léa-Linux et LinuxMag-France) | ||
<br/> | |||
<br/> | |||
'''<b>[[Dev-index|@ Retour à la rubrique Développement]]</b>''' | |||
<br/> | |||
<div class="merci">Cette page est issue de la documentation 'pré-wiki' de Léa a été convertie avec HTML::WikiConverter. Elle fut créée par Xavier GARREAU le 16/02/2002.</div> | <div class="merci">Cette page est issue de la documentation 'pré-wiki' de Léa a été convertie avec HTML::WikiConverter. Elle fut créée par Xavier GARREAU le 16/02/2002.</div> |
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/ |