这个问题在其他论坛上以不同的方式提出过。但是还没有一个合适的解释为什么你不能在 bash 中执行以下操作。
#!/bin/bash
command1
SWITCH_USER_TO rag
command2
command3
通常,建议的方式是
#!/bin/bash
command1
sudo -u rag command2
sudo -u rag command3
但是为什么在 bash 脚本执行期间的某个时刻无法bash
更改为不同的用户并以不同的用户身份执行其余命令?
答案1
信息已经在我的另一个答案,但它有点埋在那里。所以我想,我应该在这里添加它。
bash
没有更改用户的规定,但zsh
有。
在 中zsh
,您可以通过为这些变量赋值来更改用户:
$EUID
:更改有效用户 ID(仅此而已)。通常,您可以在真实用户 ID 和保存的设置用户 ID 之间更改您的 euid(如果从 setuid 可执行文件调用),或者如果您的 euid 为 0,则更改为任何内容。$UID
:将有效用户id、真实用户id和保存设置用户id更改为新值。除非新值是 0,否则就不会再回来,因为一旦所有 3 个值都设置为相同的值,就无法将其更改为其他值。$EGID
和$GID
:同样的事情,但对于组 ID。$USERNAME
。这就像使用sudo
orsu
。它将您的 euid、ruid、ssuid 设置为该用户的 uid。它还根据用户数据库中定义的组成员身份设置egid、rgid 和ssgid 以及补充组。与 for 一样$UID
,除非您设置$USERNAME
为root
,否则不会返回,但与 for 一样$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
现在,要像sudo
或 那样更改用户su
,我们只能通过使用子 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
)
答案2
内核优惠man 2 setuid
啦。
现在,它适用于调用过程。更重要的是你不能提升您的特权。这就是为什么su
设置sudo
了 SETUID 位,以便它们始终以最高权限 ( root
) 运行,并相应地降至所需的用户。
这种组合意味着您不能通过运行其他程序来更改 shell UID(这就是为什么您不能期望sudo
或任何其他程序执行此操作)并且您不能(作为 shell)自己执行此操作,除非您愿意以 root 或与您想要切换到的用户身份运行,但这是没有意义的。而且一旦你放弃特权,就没有回头路了。
答案3
好吧,你总是可以这样做:
#! /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)"
(‿ʘ)
这首先是一个笑话,因为它实现了所要求的内容,尽管可能不完全符合预期,并且实际上没有用。但随着它演变成某种在某种程度上有效并涉及一些不错的技巧的东西,这里有一些解释:
作为米罗斯拉夫说,如果我们抛开 Linux 风格能力(无论如何,这对这里也没有什么帮助),非特权进程更改 uid 的唯一方法是执行 setuid 可执行文件。
一旦您获得超级用户权限(例如,通过执行所有者为 root 的 setuid 可执行文件),您可以在原始用户 ID、0 和任何其他 ID 之间来回切换有效用户 ID,除非您放弃保存的设置用户 ID(就像类似sudo
或通常做的事情一样su
)。
例如:
$ 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,而真实用户 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 0
C中使用的形式,这是一种完全注释掉某些代码的方式。
:
是返回 true 的无操作。所以在 中: || :
,第二个:
永远不会被执行。然而,它被解析了。这<< 'xxx'
是此处文档的一种形式,其中(因为xxx
被引用),不进行任何扩展或解释。
我们本可以这样做:
: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
但这意味着必须编写此处文档并将其作为标准输入传递到:
.:||:
避免了这种情况。
现在,它变得棘手的是我们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
通常会清理它的环境并且不允许传递任意环境变量)。它将将该变量的内容评估为 bash 代码并获取脚本本身。bash
_x
env
sudo
bash
$_x
_x
之前定义为:
_x="$(declare;alias;shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a'
所有declare
, alias
,shopt -p
set +o
输出构成了 shell 内部状态的转储。也就是说,它们将所有变量、函数、别名和选项的定义转储为可供评估的 shell 代码。最重要的是,我们根据数组的值添加位置参数( $1
, ...)的设置(见下文),并进行一些清理,以便大变量不会在剩余的时间内保留在环境中脚本的。$2
$_a
$_x
您会注意到,第一部分包含set +x
在一个命令组中,其 stderr 被重定向到/dev/null
( {...} 2> /dev/null
)。这是因为,如果在脚本set -x
(或set -o xtrace
)中的某个时刻运行,我们不希望该前导码生成跟踪,因为我们希望使其尽可能少地具有侵入性。因此,我们运行set +x
(在确保事先转储选项(包括xtrace
)设置之后)将跟踪发送到 /dev/null。
出于类似的原因, stderreval "$_X"
也被重定向到 /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 也是为了隐藏 xtraces,这是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
只有当您有权sudo
asalice
并alice
有权sudo
asbob
和bob
as 时,这才有效root
。
所以,在实践中,这并不是很有用。使用su
代替sudo
(或对目标用户而不是调用者进行身份验证sudo
的配置sudo
)可能更有意义,但这仍然意味着您需要知道所有这些人的密码。
答案4
通过执行“sh”或 shell 命令,可以使用“sudo -u ram sh”命令更改为“ram”用户。 “退出”命令将使您返回到原始用户。