¿Por qué no podemos ejecutar una lista de comandos como usuario diferente sin sudo?

8

Esta pregunta se ha formulado de manera diferente en otros foros. Pero no ha habido una explicación decente por la que no puedes hacer lo siguiente en bash.

#!/bin/bash
command1
SWITCH_USER_TO rag
command2
command3

Por lo general, la forma sugerida es

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

pero ¿por qué no es posible bashcambiar a un usuario diferente en algún momento durante la ejecución del script bash y ejecutar el resto de los comandos como un usuario diferente?

trapo
fuente
Esta pregunta también se hizo en Superuser, en superuser.com/questions/93385/… , y esta respuesta superuser.com/a/573882/76251 proporciona un método similar a HERE DOC para cambiar usuarios dentro de un script.
Tim Kennedy

Respuestas:

4

La información ya está en mi otra respuesta , pero está un poco enterrada allí. Entonces pensé, lo agregaría aquí.

bashno tiene provisión para cambiar usuarios, pero zshsí.

En zsh, cambia usuarios asignando valores a esas variables:

  • $EUID: cambia la identificación de usuario efectiva (y nada más). Por lo general, puede cambiar su euid entre su ID de usuario real y el ID de usuario del conjunto guardado (si se llama desde un ejecutable setuid) o cambiar a cualquier cosa si su euid es 0.
  • $UID: cambia la identificación de usuario efectiva, la identificación de usuario real y la identificación de usuario establecida guardada al nuevo valor A menos que ese nuevo valor sea 0, no habrá regreso, ya que una vez que los 3 se hayan establecido en el mismo valor, no hay forma de cambiarlo a otra cosa.
  • $EGIDy $GID: lo mismo pero para identificadores de grupo.
  • $USERNAME. Eso es como usar sudoo su. Establece su euid, ruid, ssuid al uid de ese usuario. También establece los grupos egid, rgid y ssgid y suplementarios en función de las membresías de grupo como se define en la base de datos del usuario. Al igual que para $UID, a menos que establezca $USERNAMEque root, no hay vuelta atrás, pero al igual que para el $UID, puede cambiar el usuario sólo para un subnivel.

Si ejecuta estos scripts como "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

Ahora, para cambiar de usuario como en sudoo su, solo podemos hacerlo usando subshells, de lo contrario solo podríamos hacerlo una vez:

#! /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
fuente
8

Ofertas de kernel man 2 setuidy amigos.

Ahora, funciona en el proceso de llamadas. Más importante aún, no puede elevar sus privilegios. Es por eso suy sudotienen bit setuid de manera que corran siempre con privilegios más altos ( root) y caen a consecuencia de usuario deseado.

Esa combinación significa que no puede cambiar el UID del shell ejecutando algún otro programa para hacer eso (es por eso que no puede esperar sudoni ningún otro programa para hacerlo) y no puede hacerlo (como shell) a menos que están dispuestos a ejecutarse como root o el mismo usuario al que desea cambiar, lo que no tiene sentido. Además, una vez que abandonas los privilegios, no hay vuelta atrás.

Miroslav Koškár
fuente
6

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 sudoo que susuelen hacer).

Por ejemplo:

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

Ahora tengo un envcomando 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

perltiene 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 idcomandos se invocan con un ID de usuario real y un ID de usuario del conjunto guardado de 0 (aunque si hubiera usado mi en ./envlugar de sudoeso, 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.

bashno tiene tal forma de cambiar los identificadores de usuario. Entonces, incluso si tuviera un ejecutable setuid con el que llamar a su bashscript, 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 someuseres 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 0utilizada 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 xxxse 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 bashexpande los alias muy temprano en su proceso de análisis. Tener skipun 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) bashcon la _xvariable en su entorno (lo usamos envaquí porque sudogeneralmente desinfecta su entorno y no permite el paso de entornos arbitrarios). Eso bashevalúa el contenido de esa $_xvariable 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 +osalida 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 $_amatriz (ver más abajo), y algunos limpian para que la gran $_xvariable no permanezca en el entorno durante el resto del guión

Notarás que la primera parte hasta set +xestá 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 xtraceconfiguració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 bashque 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 evales evitar un error bash. Entonces llamamos a la SWITCH_TO_USER función .

${_u+:} alias skip=:

Esa parte cancela el skipalias (lo reemplaza con el :comando no-op) a menos que se establezca la $_uvariable.

skip

Ese es nuestro skipalias. 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 rootusuario, y el script restaurará el estado guardado, saltaremos a esa SWITCH_TO_USER rootlínea y continuaremos.

Lo que eso significa es que debe escribirse exactamente como stat, con SWITCH_TO_USERal 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 sudonormalmente 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 sudoas alicey alicetiene derecho a sudoas boby bobas root.

Entonces, en la práctica, eso no es realmente útil. Usar en sulugar de sudo(o una sudoconfiguración donde se sudoautentique 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.

Stéphane Chazelas
fuente
¿puedes explicar qué hace tu guión?
trapo
@rag, lo pediste ...
Stéphane Chazelas
1
¿Esa es tu entrada para el ioshcc? Deberías haber ingresado solo en una línea.
ott--
-2

El comando "sudo -u ram sh" se puede usar para cambiar al usuario "ram", ejecutando el comando "sh" o shell. El comando "salir" lo llevará de regreso al usuario original.

usuario45185
fuente