« SUID scripts » : différence entre les versions

De Lea Linux
Aller à la navigation Aller à la recherche
Aucun résumé des modifications
m (Lea a déplacé la page Dev-suid scripts vers SUID scripts)
 
(Une version intermédiaire par un autre utilisateur non affichée)
Ligne 8 : 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 <code>chmod 4755</code>. Lisez donc encore quelques lignes avant de m'envoyer un mail d'insultes.
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 14 : 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 ></code>
  < 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/</code>
  [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 37 : 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</code>
  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</code><br /> - Qui a dit ça ? Bill ? A la porte, tout de suite !"''<br />
: "''- 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 50 : 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 62 : Ligne 62 :
   return errno;
   return errno;
  }
  }
  </code>
  </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 78 : 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</code>
  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());</code> comme ci-dessous :
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 97 : Ligne 97 :
   printf ("Error : %d\n", errno);
   printf ("Error : %d\n", errno);
   return errno;
   return errno;
  }</code>
  }</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 107 : 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</code>
  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</code> à 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.
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</code>. Les fonctions <code>seteuid</code>, <code>setuid</code> et <code>setreuid</code> servent à manipuler les UID et EUID d'un programme. <code>setuid</code> modifie prend un paramètre qu'il affecte à l'UID et l'EUID. <code>seteuid</code> prend un paramètre qu'il affecte à l'EUID. <code>setreuid</code> 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''' <code>setuid(ID)</code> est équivalent à <code>setreuid(ID, ID)</code> et <code>seteuid(ID)</code> est équivalent à <code>setreuid(-1, ID)</code>.
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 137 : Ligne 137 :
   printf ("Error : %d\n", errno);
   printf ("Error : %d\n", errno);
   return errno;
   return errno;
  }</code>
  }</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 152 : 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</code>
  .Xdefaults  .bashrc        .tcshrc        .zshrc</div>


Vous voyez qu'après l'appel à <code>setreuid</code>, 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...
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</code> 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 :
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 191 : Ligne 191 :
  fclose(fd);
  fclose(fd);
   }
   }
  }</code>
  }</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 204 : 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]$</code>
  [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/</code>, dans le fichier <code>liste</code>. 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 :
'''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 215 : Ligne 215 :
  < Ctrl+D >
  < Ctrl+D >
  [root@ZAZOU .authoscripts]#
  [root@ZAZOU .authoscripts]#
  [root@ZAZOU .authoscripts]# mv /usr/bin/voir_rep_root ./</code>
  [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 262 : Ligne 262 :
   printf ("Erreur : %d - %s\n", errno, strerror(errno));
   printf ("Erreur : %d - %s\n", errno, strerror(errno));
   return errno;
   return errno;
  }</code>
  }</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</code>
  [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 283 : 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</code>
  Params : 1 2 3 4 5 6 7 8 9 0</div>


== En savoir plus ==
== En savoir plus ==


<code>man execve</code> et entre autres, la section NOTES :
<div class="code">man execve</div> et entre autres, la section NOTES :


  NOTES
  NOTES
Ligne 299 : Ligne 299 :
Vous avez compris le pourquoi de cet article ?
Vous avez compris le pourquoi de cet article ?


<code>man getuid</code>
<div class="code">man getuid</div>


<code>man setuid</code>
<div class="code">man setuid</div>


<code>man setreuid</code>
<div class="code">man setreuid</div>


<code>man getlogin</code>
<div class="code">man getlogin</div>


<code>man cuserid</code>
<div class="code">man cuserid</div>


<code>man chmod</code>
<div class="code">man chmod</div>


== Tshaw ==
== Tshaw ==

Dernière version du 1 décembre 2023 à 17:47


SUID Scripts

SUID Scripts
Cet article fait suite à une question qui avait été débattue sur la liste lea_aide@club.voila.fr au premier semestre 2001. Comme ce sujet m'avait passionné et qu'il pourrait intéresser d'autres personnes, j'ai écrit cet article.
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 ;-) ) :

[root@ZAZOU /root]# cat > voir_rep_root
#!/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 ! :

[root@ZAZOU /root]# chmod 4755 voir_rep_root [root@ZAZOU /root]# mv voir_rep_root /usr/bin/

(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)

[root@ZAZOU /root]# voir_rep_root
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 xavier
ls: /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 <unistd.h>
#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]# gcc -o lanceur_de_script lanceur_de_script.c
[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 xavier
ls: /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 :

printf ("UID %d - EUID %d\n", getuid(), geteuid());

comme ci-dessous :

#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) {
  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]# chmod 4111 lanceur_de_script
[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 xavier
ls: /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

/etc/passwd

à 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

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_2.c :

#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) {
  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]# gcc -o lanceur_de_script_2 lanceur_de_script_2.c
[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 à

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 ! 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

setreuid

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 <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@ZAZOU /root]# gcc -o createur_de_fichier createur_de_fichier.c
[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

/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@ZAZOU /root]# mkdir .authoscripts
[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 <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@ZAZOU /root]# gcc -o suscript suscript.c
[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 /root]# cd .authoscripts/
[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: root
Params : 1 2 3 4 5 6 7 8 9 0

En savoir plus

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.

Vous avez compris le pourquoi de cet article ?

man getuid
man setuid
man setreuid
man getlogin
man cuserid
man chmod

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


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.

Copyright

Copyright © 16/02/2002, Xavier GARREAU

Creative Commons License
Creative Commons Attribution iconCreative Commons Share Alike iconCreative Commons Noncommercial
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/