如果没有sudo,为什么我们不能以其他用户身份执行命令列表?


8

在其他论坛中已经以不同的方式提出了这个问题。但是还没有一个很好的解释,为什么不能用bash进行以下操作。

#!/bin/bash
command1
SWITCH_USER_TO rag
command2
command3

通常,建议的方法是

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

但是为什么不能在bash执行bash脚本期间的某个时候更改为其他用户,并以其他用户身份执行其余命令?


在Superuser上也曾在superuser.com/questions/93385/…上问过这个问题,这个答案superuser.com/a/573882/76251提供了一种类似于HERE DOC的方法,用于在脚本中更改用户。
肯尼迪

Answers:


4

该信息已经在我的其他答案中,但是有点埋在这里。所以我想,我要在这里添加。

bash没有更改用户的规定,但是zsh有。

在中zsh,您可以通过为这些变量分配值来更改用户:

  • $EUID:更改有效的用户ID(仅此而已)。通常,您可以在真实用户ID和保存的设置用户ID(如果从setuid可执行文件调用)之间更改euid,或者如果euid为0,则更改为任何内容。
  • $UID:将有效用户ID,实际用户ID和已保存的设置用户ID更改为新值。除非新值是0,否则不会返回,因为一旦将所有3个都设置为相同的值,就无法将其更改为其他任何值。
  • $EGID$GID:相同,但组ID相同。
  • $USERNAME。就像使用sudosu。它将您的euid,ruid,ssuid设置为该用户的uid。它还根据用户数据库中定义的组成员身份来设置egid,rgid和ssgid以及补充组。喜欢$UID,除非你设置$USERNAMEroot,有没有回来了,但像$UID,您可以更改用户只对一个子shell。

如果您以“ 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

现在,要将用户更改为sudosu,我们只能使用子shell来执行此操作,否则只能执行一次:

#! /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
)

8

内核提供man 2 setuid和朋友。

现在,它可以在调用过程中使用。更重要的是,您无法提升特权。这就是为什么susudo设置了SETUID位,以便它们始终以最高特权(root)运行并相应地移至所需的用户。

这种结合意味着您不能通过运行其他程序来更改外壳UID(这就是为什么您不能期望sudo或任何其他程序这样做),并且除非您这样做,否则您(作为外壳)不能自己做愿意以root用户或您要切换到的用户身份运行,这没有任何意义。而且,一旦您放弃特权,就没有回头路了。


6

好吧,您可以始终这样做:

#! /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)"

(ʘ‿ʘ)

最初是开个玩笑,尽管实现了所要求的功能,但可能并不完全符合预期,并且实际上没有用。但是随着它的发展到一定程度,并且涉及到一些不错的技巧,这里有一些解释:

正如Miroslav所说,如果我们撇开Linux风格的功能(无论如何在这里还是无济于事),无特权的进程更改uid的唯一方法是执行setuid可执行文件。

但是,一旦获得超级用户特权(例如,通过执行所有者为root的setuid可执行文件),就可以在原始用户ID,0和任何其他ID之间来回切换有效用户ID,除非您放弃已保存的设置用户ID(喜欢的东西sudosu通常做的事情)。

例如:

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

现在,我有了一个env命令,该命令使我可以使用有效的用户ID和保存的设置用户ID 0(我的实际用户ID仍为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具有setuid/的包装seteuid(这些$>$<变量)。

zsh也是如此:

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

尽管在上面这些id命令是使用真实用户ID和保存的集合用户ID调用的(为0)(尽管如果我使用my ./env来代替,sudo那只会是保存的集合用户ID,而真实的用户ID仍为1000),这意味着如果它们是不受信任的命令,它们仍然可能会造成一些损害,因此您应该改为编写它:

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

(即设置所有uid(有效,实数和保存集)仅用于执行这些命令。

bash没有任何更改用户ID的方法。因此,即使您有一个setuid可执行文件来调用bash脚本,也无济于事。

使用bash,每次要更改uid时,您都需要执行setuid可执行文件。

上面脚本中的想法是在调用SWITCH_TO_USER时执行一个新的bash实例以执行该脚本的其余部分。

SWITCH_TO_USER someuser或多或少是一个功能,可以以其他用户的身份再次执行脚本(使用sudo),但跳过脚本的开始直到SWITCH_TO_USER someuser

棘手的是,我们要在以其他用户身份启动新bash之后保持当前bash的状态。

让我们分解一下:

{ shopt -s expand_aliases;

我们需要别名。此脚本中的技巧之一是SWITCH_TO_USER someuser使用类似以下内容将脚本部分跳过到为止:

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

这种形式类似于#if 0C语言中使用的形式,这是一种完全注释掉某些代码的方式。

:是一个没有操作的返回true。因此,在中: || :,第二个:永远不会执行。但是,它被解析。并且<< 'xxx'是此处文档的一种形式,其中(由于xxx引用),没有进行扩展或解释。

我们可以做到:

: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER

但这意味着必须编写here-document并将其作为stdin传递给::||:避免这种情况。

现在,它变得骇人听闻的是,我们利用了bash在解析过程的早期就扩展别名的事实。有skip作为的别名:||: << 'SWITCH_TO_USER someuther'的一部分被注释掉结构。

让我们继续:

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";}

这是SWITCH_TO_USER 函数的定义。我们将在下面看到SWITCH_TO_USER最终将成为该函数的别名。

该函数执行大量的重新执行脚本。最终,我们看到它使用环境中的变量重新执行(由于与之相同的过程exec)(我们在这里使用它是因为通常会清理其环境,并且不允许传递任意env vars)。这会将变量的内容评估为bash代码,并提供脚本本身。bash_xenvsudobash$_x

_x 之前定义为:

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

所有的declarealiasshopt -p set +o输出弥补外壳的内部状态的转储。也就是说,它们将所有变量,函数,别名和选项的定义转储为准备好进行评估的shell代码。最重要的是,我们根据数组的值添加位置参数($1$2...)的设置$_a(请参见下文),并进行一些清理,$_x以使剩余的巨大变量不会留在环境中的脚本。

您会注意到,直到第一部分set +x都包装在一个命令组中,该命令组的stderr重定向到/dev/null{...} 2> /dev/null)。这是因为,如果在脚本set -x(或set -o xtrace)中的某个时刻运行,我们不希望该前导码生成跟踪,因为我们希望使其尽可能少地侵入。因此,我们在将跟踪发送到/ dev / null之前运行了一个set +x(确保xtrace事先转储选项(包括)设置之后)。

eval "$_X"出于类似原因,stderr也被重定向到/ dev / null,同时也避免了有关尝试写入特殊只读变量的错误。

让我们继续执行脚本:

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

这就是我们上面描述的技巧。在初始脚本调用时,它将被取消(请参见下文)。

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

现在,别名包装器围绕SWITCH_TO_USER。主要原因是能够将位置参数($1$2...)传递给新的bash,它将解释脚本的其余部分。我们无法在SWITCH_TO_USER 函数中执行此操作,因为在函数内部是函数"$@"的参数,而不是脚本的参数。将stderr重定向到/ dev / null再次是为了隐藏xtrace,并且eval是用来解决中的错误bash。然后我们调用SWITCH_TO_USER 函数

${_u+:} alias skip=:

除非设置了变量,否则该部分将取消skip别名(用:no-op命令替换)$_u

skip

那是我们的skip别名。第一次调用时,它将是:(无操作)。关于子序列的重新调用,将类似于::||: << 'SWITCH_TO_USER root'

echo test
a=foo
set a b

SWITCH_TO_USER root

因此,在此作为示例,此时,我们以root用户身份重新调用脚本,脚本将还原保存的状态,并跳到该SWITCH_TO_USER root行并继续。

这意味着它必须完全像stat一样写,SWITCH_TO_USER在行的开头,并且参数之间必须有一个空格。

大多数状态(stdin,stdout和stderr)将被保留,但其他文件描述符则不会被保留,因为sudo除非明确配置为不这样做,否则通常会关闭它们。因此,例如:

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

通常将无法正常工作。

另请注意,如果您这样做:

SWITCH_TO_USER alice
SWITCH_TO_USER bob
SWITCH_TO_USER root

如果你有正确的那只能sudoalicealice有权利sudobob,和bob作为root

因此,在实践中,这并不是真正有用的。使用su代替sudo(或使用对目标用户而不是调用方sudo进行sudo身份验证的配置)可能更有意义,但这仍意味着您需要了解所有这些人的密码。


您能解释一下您的脚本在做什么吗?
抹布

@rag,你自找的......
斯特凡Chazelas

1
那是您的参赛作品ioshcc吗?您应该只输入一行。
ott--

-2

通过执行“ sh”或shell命令,可以使用“ sudo -u ram sh”命令更改为“ ram”用户。“退出”命令将使您回到原始用户。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.