Pourquoi ne pouvons-nous pas exécuter une liste de commandes en tant qu'utilisateur différent sans sudo?

8

Cette question a été posée d'une manière différente dans d'autres forums. Mais il n'y a pas eu d'explication décente pour laquelle vous ne pouvez pas faire ce qui suit en bash.

#!/bin/bash
command1
SWITCH_USER_TO rag
command2
command3

Habituellement, la manière suggérée est

#!/bin/bash
command1
sudo -u rag command2
sudo -u rag command3

mais pourquoi n'est-il pas possible bashde passer à un autre utilisateur à un moment donné lors de l'exécution du script bash et d'exécuter le reste des commandes en tant qu'utilisateur différent?

chiffon
la source
Cette question a également été posée sur Superuser, à superuser.com/questions/93385/… , et cette réponse superuser.com/a/573882/76251 fournit une méthode ICI DOC pour changer les utilisateurs dans un script.
Tim Kennedy

Réponses:

4

Les informations figurent déjà dans mon autre réponse , mais elles sont un peu enfouies là-bas. Alors j'ai pensé, je l'ajouterais ici.

bashn'a pas de disposition pour changer les utilisateurs, mais le zshfait.

Dans zsh, vous modifiez les utilisateurs en affectant des valeurs à ces variables:

  • $EUID: modifiez l'ID utilisateur effectif (et rien d'autre). Habituellement, vous pouvez changer votre euid entre votre véritable ID utilisateur et l'ID utilisateur de l'ensemble enregistré (s'il est appelé à partir d'un exécutable setuid) ou changer n'importe quoi si votre euid est 0.
  • $UID: modifiez l'ID utilisateur effectif, l'ID utilisateur réel et l'ID utilisateur d'ensemble enregistré à la nouvelle valeur. À moins que cette nouvelle valeur soit 0, il n'y a pas de retour, car une fois que tous les 3 ont été définis sur la même valeur, il n'y a aucun moyen de la changer pour autre chose.
  • $EGIDet $GID: même chose mais pour les identifiants de groupe.
  • $USERNAME. C'est comme utiliser sudoou su. Il définit votre euid, ruid, ssuid sur l'uid de cet utilisateur. Il définit également les groupes egid, rgid et ssgid et supplémentaires en fonction des appartenances aux groupes définies dans la base de données des utilisateurs. Comme pour $UID, à moins que vous définissez $USERNAMEà root, il n'y a pas de retour, mais comme pour $UID, vous pouvez changer l'utilisateur que pour un sous - shell.

Si vous exécutez ces scripts en tant que "root":

#! /bin/zsh -
UID=0 # make sure all our uids are 0

id -u # run a command as root

EUID=1000

id -u # run a command as uid 1000 (but the real user id is still 0
      # so that command would be able to change its euid to that.
      # As for the gids, we only have those we had initially, so 
      # if started as "sudo the-script", only the groups root is a
      # member of.

EUID=0 # we're allowed to do that because our ruid is 0. We need to do
       # that because as a non-priviledged user, we can't set our euid
       # to anything else.

EUID=1001 # now we can change our euid since we're superuser again.

id -u # same as above

Maintenant, pour changer d'utilisateur comme dans sudoou su, nous ne pouvons le faire qu'en utilisant des sous-coquilles, sinon nous ne pourrions le faire qu'une seule fois:

#! /bin/zsh -

id -u # run as root

(
  USERNAME=rag
  # that's a subshell running as "rag"

  id # see all relevant group memberships are applied
)
# now back to the parent shell process running as root

(
  USERNAME=stephane
  # another subshell this time running as "stephane"

  id
)
Stéphane Chazelas
la source
8

Offres de noyau man 2 setuidet amis.

Maintenant, cela fonctionne sur le processus d'appel. Plus important encore, vous ne pouvez pas élever vos privilèges. C'est pourquoi suet sudoque le bit SETUID est défini pour qu'ils s'exécutent toujours avec les privilèges les plus élevés ( root) et tombent sur l'utilisateur souhaité en conséquence.

Cette combinaison signifie que vous ne pouvez pas changer l'UID du shell en exécutant un autre programme pour le faire (c'est pourquoi vous ne pouvez pas vous attendre à ce sudoqu'un autre programme le fasse) et vous ne pouvez pas (en tant que shell) le faire vous-même à moins que vous sont prêts à s'exécuter en tant que superutilisateur ou en tant que même utilisateur vers lequel vous souhaitez basculer, ce qui n'a pas de sens. De plus, une fois que vous avez supprimé les privilèges, il n'y a plus de retour possible.

Miroslav Koškár
la source
6

Eh bien, vous pouvez toujours faire:

#! /bin/bash -
{ shopt -s expand_aliases;SWITCH_TO_USER(){ { _u=$*;_x="$(declare;alias
shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a';set +x;} 2>/dev/null
exec sudo -u "$1" env "_x=$_x" bash -c 'eval "$_x" 2> /dev/null;. "$0"
' "$0";};alias skip=":||:<<'SWITCH_TO_USER $_u'"
alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"
${_u+:} alias skip=:;} 2>/dev/null
skip

echo test
a=foo
set a b

SWITCH_TO_USER root

echo "$a and $1 as $(id -un)"
set -x
foo() { echo "bar as $(id -un)"; }

SWITCH_TO_USER rag

foo
set +x

SWITCH_TO_USER root again

echo "hi again from $(id -un)"

(ʘ‿ʘ)

Cela a d'abord commencé comme une blague car cela implémente ce qui est demandé, mais probablement pas exactement comme prévu, et n'est pas pratique. Mais comme cela a évolué vers quelque chose qui fonctionne dans une certaine mesure et implique quelques bons hacks, voici une petite explication:

Comme l'a dit Miroslav , si nous laissons de côté les capacités de style Linux (qui ne seraient pas vraiment utiles ici non plus), la seule façon pour un processus non privilégié de changer l'uid est d'exécuter un exécutable setuid.

Une fois que vous obtenez le privilège de superutilisateur (en exécutant un exécutable setuid dont le propriétaire est root par exemple), vous pouvez basculer l'ID utilisateur effectif entre votre ID utilisateur d'origine, 0 et tout autre ID, sauf si vous renoncez à votre ID utilisateur d'ensemble enregistré ( comme des choses comme sudoou sufont typiquement).

Par exemple:

$ sudo cp /usr/bin/env .
$ sudo chmod 4755 ./env

Maintenant, j'ai une envcommande qui me permet d'exécuter n'importe quelle commande avec un identifiant utilisateur efficace et un identifiant utilisateur enregistré de 0 (mon identifiant réel étant toujours 1000):

$ ./env id -u
0
$ ./env id -ru
1000
$ ./env -u PATH =perl -e '$>=1; system("id -u"); $>=0;$>=2; system("id -u");
   $>=0; $>=$<=3; system("id -ru; id -u"); $>=0;$<=$>=4; system("id -ru; id -u")'
1
2
3
3
4
4

perla des wrappers pour setuid/ seteuid(ceux-ci $>et les $<variables).

Zsh aussi:

$ sudo zsh -c 'EUID=1; id -u; EUID=0; EUID=2; id -u'
1
2

Bien que ci-dessus, ces idcommandes soient appelées avec un identifiant utilisateur réel et un identifiant utilisateur enregistré de 0 (bien que si j'avais utilisé mon ./envau lieu de sudocela, cela n'aurait été que l'identifiant utilisateur enregistré, tandis que l'ID utilisateur réel serait resté 1000), ce qui signifie que s'il s'agissait de commandes non fiables, elles pourraient toujours faire des dégâts, vous devriez donc l'écrire à la place comme:

$ sudo zsh -c 'UID=1 id -u; UID=2 id -u'

(c'est-à-dire tous les uids (ensemble effectif, réel et enregistré) juste pour l'exécution de ces commandes.

bashn'a aucun moyen de modifier les identifiants utilisateur. Donc, même si vous aviez un exécutable setuid avec lequel appeler votre bashscript, cela n'aiderait pas.

Avec bash, il vous reste à exécuter un exécutable setuid chaque fois que vous voulez changer d'uid.

L'idée dans le script ci-dessus est lors d'un appel à SWITCH_TO_USER, pour exécuter une nouvelle instance bash pour exécuter le reste du script.

SWITCH_TO_USER someuserest plus ou moins une fonction qui exécute à nouveau le script en tant qu'utilisateur différent (en utilisant sudo) mais en ignorant le début du script jusqu'à SWITCH_TO_USER someuser.

Là où cela devient difficile, c'est que nous voulons conserver l'état de la bash actuelle après avoir démarré la nouvelle bash en tant qu'utilisateur différent.

Décomposons-le:

{ shopt -s expand_aliases;

Nous aurons besoin d'alias. L'une des astuces de ce script consiste à ignorer la partie du script jusqu'au SWITCH_TO_USER someuser, avec quelque chose comme:

:||: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER

Ce formulaire est similaire à celui #if 0utilisé en C, c'est un moyen de commenter complètement du code.

:est un no-op qui retourne vrai. Donc en : || :, le second :n'est jamais exécuté. Cependant, il est analysé. Et << 'xxx'c'est une forme de document ici où (parce que xxxc'est cité), aucune expansion ou interprétation n'est faite.

On aurait pu faire:

: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER

Mais cela aurait signifié que le document ici aurait dû être écrit et transmis en tant que stdin à :. :||:évite cela.

Maintenant, où il devient hacky, c'est que nous utilisons le fait que les bashalias se développent très tôt dans son processus d'analyse. Avoir skipun alias pour la :||: << 'SWITCH_TO_USER someuther'partie de la construction de mise en commentaire .

Continuons:

SWITCH_TO_USER(){ { _u=$*;_x="$(declare;alias
shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a';set +x;} 2>/dev/null
exec sudo -u "$1" env "_x=$_x" bash -c 'eval "$_x" 2> /dev/null;. "$0"
' "$0";}

Voici la définition de la fonction SWITCH_TO_USER . Nous verrons ci-dessous que SWITCH_TO_USER sera éventuellement un alias enroulé autour de cette fonction.

Cette fonction fait l'essentiel de la réexécution du script. En fin de compte, nous voyons qu'il se ré-exécute (dans le même processus à cause de exec) bashavec la _xvariable dans son environnement (nous l'utilisons envici car sudogénéralement assainit son environnement et ne permet pas de passer des vars env arbitraires à travers). Cela bashévalue le contenu de cette $_xvariable en tant que code bash et source le script lui-même.

_x est défini précédemment comme:

_x="$(declare;alias;shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a'

Toutes les declare, alias, shopt -p set +osortie constituent une décharge de l'état interne de la coquille. Autrement dit, ils vident la définition de toutes les variables, fonctions, alias et options sous forme de code shell prêt à être évalué. En plus de cela, nous ajoutons le réglage des paramètres de position ( $1, $2...) en fonction de la valeur du $_atableau (voir ci-dessous), et un peu de nettoyage afin que l'énorme $_xvariable ne reste pas dans l'environnement pour le reste du script.

Vous remarquerez que la première partie jusqu'à set +xest enveloppée dans un groupe de commandes dont stderr est redirigé vers /dev/null( {...} 2> /dev/null). C'est parce que, si à un moment donné du script set -x(ou set -o xtrace) est exécuté, nous ne voulons pas que ce préambule génère des traces car nous voulons le rendre le moins intrusif possible. Nous exécutons donc un set +x(après avoir pris soin de vider l'option (y compris xtrace) les paramètres au préalable) où les traces sont envoyées à / dev / null.

Le eval "$_X"stderr est également redirigé vers / dev / null pour des raisons similaires, mais également pour éviter les erreurs d'écriture lors d'une tentative de lecture de variables spéciales en lecture seule.

Continuons avec le script:

alias skip=":||:<<'SWITCH_TO_USER $_u'"

C'est notre astuce décrite ci-dessus. Lors de l'appel initial du script, il sera annulé (voir ci-dessous).

alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"

Maintenant, l'encapsuleur d'alias autour de SWITCH_TO_USER. La raison principale est de pouvoir passer les paramètres positionnels ( $1, $2...) au nouveau bashqui interprétera le reste du script. Nous ne pouvions pas le faire dans la SWITCH_TO_USER fonction car à l'intérieur "$@"de la fonction, se trouvent les arguments des fonctions, pas ceux des scripts. La redirection stderr vers / dev / null consiste à nouveau à masquer les xtraces, et evalà contourner un bogue dans bash. Ensuite, nous appelons la SWITCH_TO_USER fonction .

${_u+:} alias skip=:

Cette partie annule l' skipalias (le remplace par la :commande no-op) sauf si la $_uvariable est définie.

skip

Voilà notre skipalias. Lors de la première invocation, ce sera juste :(le no-op). Sur les invocations de re-subsequence, ce sera quelque chose comme: :||: << 'SWITCH_TO_USER root'.

echo test
a=foo
set a b

SWITCH_TO_USER root

Donc, ici, à titre d'exemple, à ce stade, nous réinvoquons le script en tant rootqu'utilisateur, et le script restaurera l'état enregistré, et sautera jusqu'à cette SWITCH_TO_USER rootligne et continuera.

Cela signifie qu'il doit être écrit exactement comme stat, avec SWITCH_TO_USERau début de la ligne et avec exactement un espace entre les arguments.

La plupart des états, stdin, stdout et stderr seront conservés, mais pas les autres descripteurs de fichiers car sudogénéralement ils les ferment sauf s'ils sont explicitement configurés pour ne pas le faire. Ainsi, par exemple:

exec 3> some-file
SWITCH_TO_USER bob
echo test >&3

ne fonctionnera généralement pas.

Notez également que si vous le faites:

SWITCH_TO_USER alice
SWITCH_TO_USER bob
SWITCH_TO_USER root

Cela ne fonctionne que si vous avez le droit à sudoas aliceet alicele droit à sudoas bobet bobas root.

Donc, en pratique, ce n'est pas vraiment utile. Utiliser à la suplace de sudo(ou une sudoconfiguration où sudoauthentifie l'utilisateur cible au lieu de l'appelant) pourrait être un peu plus logique, mais cela signifierait toujours que vous auriez besoin de connaître les mots de passe de tous ces gars-là.

Stéphane Chazelas
la source
pouvez-vous expliquer ce que fait votre script?
chiffon
@rag, vous l'avez demandé ...
Stéphane Chazelas
1
Est-ce votre entrée pour le ioshcc? Vous devriez avoir entré une seule ligne.
ott--
-2

La commande "sudo -u ram sh" peut être utilisée pour passer à l'utilisateur "ram", en exécutant la commande "sh" ou shell. La commande "exit" vous ramènera à l'utilisateur d'origine.

user45185
la source