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 sudo
ou su
font typiquement).
Par exemple:
$ sudo cp /usr/bin/env .
$ sudo chmod 4755 ./env
Maintenant, j'ai une env
commande 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
perl
a 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 id
commandes soient appelées avec un identifiant utilisateur réel et un identifiant utilisateur enregistré de 0 (bien que si j'avais utilisé mon ./env
au lieu de sudo
cela, 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.
bash
n'a aucun moyen de modifier les identifiants utilisateur. Donc, même si vous aviez un exécutable setuid avec lequel appeler votre bash
script, 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 someuser
est 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 0
utilisé 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 xxx
c'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 bash
alias se développent très tôt dans son processus d'analyse. Avoir skip
un 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
) bash
avec la _x
variable dans son environnement (nous l'utilisons env
ici car sudo
généralement assainit son environnement et ne permet pas de passer des vars env arbitraires à travers). Cela bash
évalue le contenu de cette $_x
variable 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 +o
sortie 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 $_a
tableau (voir ci-dessous), et un peu de nettoyage afin que l'énorme $_x
variable ne reste pas dans l'environnement pour le reste du script.
Vous remarquerez que la première partie jusqu'à set +x
est 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 bash
qui 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' skip
alias (le remplace par la :
commande no-op) sauf si la $_u
variable est définie.
skip
Voilà notre skip
alias. 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 root
qu'utilisateur, et le script restaurera l'état enregistré, et sautera jusqu'à cette SWITCH_TO_USER root
ligne et continuera.
Cela signifie qu'il doit être écrit exactement comme stat, avec SWITCH_TO_USER
au 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 sudo
gé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 à sudo
as alice
et alice
le droit à sudo
as bob
et bob
as root
.
Donc, en pratique, ce n'est pas vraiment utile. Utiliser à la su
place de sudo
(ou une sudo
configuration où sudo
authentifie 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à.