Bueno, siempre puedes hacer:
#! /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)"
(ʘ‿ʘ)
Eso comenzó como una broma, ya que implementa lo solicitado, aunque probablemente no sea exactamente como se esperaba, y no es prácticamente útil. Pero a medida que evolucionó a algo que funciona hasta cierto punto e involucra algunos trucos agradables, aquí hay una pequeña explicación:
Como dijo Miroslav , si dejamos de lado las capacidades de estilo Linux (que de todos modos tampoco ayudaría aquí), la única forma de que un proceso sin privilegios cambie uid es ejecutando un setuid ejecutable.
Sin embargo, una vez que obtenga el privilegio de superusuario (al ejecutar un ejecutable setuid cuyo propietario es root, por ejemplo), puede cambiar la identificación de usuario efectiva de ida y vuelta entre su identificación de usuario original, 0 y cualquier otra identificación a menos que renuncie a su identificación de usuario establecida guardada ( cosas como sudo
o que su
suelen hacer).
Por ejemplo:
$ sudo cp /usr/bin/env .
$ sudo chmod 4755 ./env
Ahora tengo un env
comando que me permite ejecutar cualquier comando con un ID de usuario efectivo y un ID de usuario guardado de 0 (mi ID de usuario real sigue siendo 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
tiene envoltorios para setuid
/ seteuid
(esos $>
y $<
variables).
También lo hace zsh:
$ sudo zsh -c 'EUID=1; id -u; EUID=0; EUID=2; id -u'
1
2
Aunque por encima de esos id
comandos se invocan con un ID de usuario real y un ID de usuario del conjunto guardado de 0 (aunque si hubiera usado mi en ./env
lugar de sudo
eso, solo habría sido el ID de usuario del conjunto guardado, mientras que la ID de usuario real habría permanecido 1000), lo que significa que si fueran comandos no confiables, aún podrían causar algún daño, por lo que querrá escribirlo como:
$ sudo zsh -c 'UID=1 id -u; UID=2 id -u'
(es decir, establece todos los uids (conjunto efectivo, real y guardado) solo para la ejecución de esos comandos.
bash
no tiene tal forma de cambiar los identificadores de usuario. Entonces, incluso si tuviera un ejecutable setuid con el que llamar a su bash
script, eso no ayudaría.
Con bash
, te queda ejecutar un setuid ejecutable cada vez que quieras cambiar uid.
La idea en el script anterior es sobre una llamada a SWITCH_TO_USER, para ejecutar una nueva instancia de bash para ejecutar el resto del script.
SWITCH_TO_USER someuser
es más o menos una función que ejecuta el script nuevamente como un usuario diferente (usando sudo
) pero omitiendo el inicio del script hasta SWITCH_TO_USER someuser
.
Donde se vuelve complicado es que queremos mantener el estado de la bash actual después de haber comenzado la nueva bash como un usuario diferente.
Vamos a desglosarlo:
{ shopt -s expand_aliases;
Necesitaremos alias. Uno de los trucos en este script es omitir la parte del script hasta que SWITCH_TO_USER someuser
, con algo como:
:||: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
Esa forma es similar a la #if 0
utilizada en C, es una forma de comentar completamente algún código.
:
es un no-op que devuelve verdadero. Entonces : || :
, el segundo :
nunca se ejecuta. Sin embargo, se analiza. Y << 'xxx'
es una forma de documento aquí donde (porque xxx
se cita), no se realiza ninguna expansión o interpretación.
Podríamos haber hecho:
: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
Pero eso habría significado que el documento aquí habría tenido que escribirse y pasarse como estándar :
. :||:
evita eso.
Ahora, cuando se vuelve hacky es que usamos el hecho de que bash
expande los alias muy temprano en su proceso de análisis. Tener skip
un alias para la :||: << 'SWITCH_TO_USER someuther'
parte de la construcción de comentarios .
Sigamos:
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";}
Aquí está la definición de la función SWITCH_TO_USER . Veremos a continuación que SWITCH_TO_USER eventualmente será un alias envuelto alrededor de esa función.
Esa función hace la mayor parte de volver a ejecutar el script. Al final, vemos que se vuelve a ejecutar (en el mismo proceso debido a exec
) bash
con la _x
variable en su entorno (lo usamos env
aquí porque sudo
generalmente desinfecta su entorno y no permite el paso de entornos arbitrarios). Eso bash
evalúa el contenido de esa $_x
variable como código bash y genera el script en sí.
_x
se define anteriormente como:
_x="$(declare;alias;shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a'
Todo el declare
, alias
, shopt -p
set +o
salida forman un volcado del estado interno de la cáscara. Es decir, vuelcan la definición de todas las variables, funciones, alias y opciones como código shell listo para ser evaluado. Además de eso, agregamos la configuración de los parámetros posicionales ( $1
, $2
...) en función del valor de la $_a
matriz (ver más abajo), y algunos limpian para que la gran $_x
variable no permanezca en el entorno durante el resto del guión
Notarás que la primera parte hasta set +x
está envuelta en un grupo de comandos cuyo stderr se redirige a /dev/null
( {...} 2> /dev/null
). Esto se debe a que, si en algún momento se ejecuta el script set -x
(o set -o xtrace
), no queremos que ese preámbulo genere rastros, ya que queremos que sea lo menos intrusivo posible. Entonces ejecutamos un set +x
(después de habernos asegurado de volcar la xtrace
configuración de la opción (incluida ) de antemano) donde se envían los seguimientos a / dev / null.
El eval "$_X"
stderr también se redirige a / dev / null por razones similares, pero también para evitar los errores sobre el intento de escritura en variables especiales de solo lectura.
Continuemos con el guión:
alias skip=":||:<<'SWITCH_TO_USER $_u'"
Ese es nuestro truco descrito anteriormente. En la invocación inicial del script, se cancelará (ver más abajo).
alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"
Ahora el contenedor de alias alrededor de SWITCH_TO_USER. La razón principal es poder pasar los parámetros posicionales ( $1
, $2
...) a los nuevos bash
que interpretarán el resto del script. No pudimos hacerlo en la SWITCH_TO_USER
función porque dentro de la función "$@"
están los argumentos de las funciones, no los de los scripts. La redirección de stderr a / dev / null es nuevamente para ocultar xtraces, y eval
es evitar un error bash
. Entonces llamamos a la SWITCH_TO_USER
función .
${_u+:} alias skip=:
Esa parte cancela el skip
alias (lo reemplaza con el :
comando no-op) a menos que se establezca la $_u
variable.
skip
Ese es nuestro skip
alias. En la primera invocación, solo será :
(el no-op). En subsecuencia re-invocaciones, será algo como: :||: << 'SWITCH_TO_USER root'
.
echo test
a=foo
set a b
SWITCH_TO_USER root
Así que aquí, como ejemplo, en ese punto, volvemos a invocar el script como root
usuario, y el script restaurará el estado guardado, saltaremos a esa SWITCH_TO_USER root
línea y continuaremos.
Lo que eso significa es que debe escribirse exactamente como stat, con SWITCH_TO_USER
al principio de la línea y con exactamente un espacio entre argumentos.
La mayor parte del estado, stdin, stdout y stderr se conservarán, pero no los otros descriptores de archivo porque sudo
normalmente los cierra a menos que esté configurado explícitamente para no hacerlo. Entonces, por ejemplo:
exec 3> some-file
SWITCH_TO_USER bob
echo test >&3
Normalmente no funcionará.
También tenga en cuenta que si lo hace:
SWITCH_TO_USER alice
SWITCH_TO_USER bob
SWITCH_TO_USER root
Eso solo funciona si tiene derecho a sudo
as alice
y alice
tiene derecho a sudo
as bob
y bob
as root
.
Entonces, en la práctica, eso no es realmente útil. Usar en su
lugar de sudo
(o una sudo
configuración donde se sudo
autentique al usuario objetivo en lugar de la persona que llama) podría tener un poco más de sentido, pero eso aún significaría que necesitaría saber las contraseñas de todos esos tipos.