在其他论坛中已经以不同的方式提出了这个问题。但是还没有一个很好的解释,为什么不能用bash进行以下操作。
#!/bin/bash
command1
SWITCH_USER_TO rag
command2
command3
通常,建议的方法是
#!/bin/bash
command1
sudo -u rag command2
sudo -u rag command3
但是为什么不能在bash
执行bash脚本期间的某个时候更改为其他用户,并以其他用户身份执行其余命令?
在其他论坛中已经以不同的方式提出了这个问题。但是还没有一个很好的解释,为什么不能用bash进行以下操作。
#!/bin/bash
command1
SWITCH_USER_TO rag
command2
command3
通常,建议的方法是
#!/bin/bash
command1
sudo -u rag command2
sudo -u rag command3
但是为什么不能在bash
执行bash脚本期间的某个时候更改为其他用户,并以其他用户身份执行其余命令?
Answers:
该信息已经在我的其他答案中,但是有点埋在这里。所以我想,我要在这里添加。
bash
没有更改用户的规定,但是zsh
有。
在中zsh
,您可以通过为这些变量分配值来更改用户:
$EUID
:更改有效的用户ID(仅此而已)。通常,您可以在真实用户ID和保存的设置用户ID(如果从setuid可执行文件调用)之间更改euid,或者如果euid为0,则更改为任何内容。$UID
:将有效用户ID,实际用户ID和已保存的设置用户ID更改为新值。除非新值是0,否则不会返回,因为一旦将所有3个都设置为相同的值,就无法将其更改为其他任何值。$EGID
和$GID
:相同,但组ID相同。$USERNAME
。就像使用sudo
或su
。它将您的euid,ruid,ssuid设置为该用户的uid。它还根据用户数据库中定义的组成员身份来设置egid,rgid和ssgid以及补充组。喜欢$UID
,除非你设置$USERNAME
到root
,有没有回来了,但像$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
)
内核提供man 2 setuid
和朋友。
现在,它可以在调用过程中使用。更重要的是,您无法提升特权。这就是为什么su
并sudo
设置了SETUID位,以便它们始终以最高特权(root
)运行并相应地移至所需的用户。
这种结合意味着您不能通过运行其他程序来更改外壳UID(这就是为什么您不能期望sudo
或任何其他程序这样做),并且除非您这样做,否则您(作为外壳)不能自己做愿意以root用户或您要切换到的用户身份运行,这没有任何意义。而且,一旦您放弃特权,就没有回头路了。
好吧,您可以始终这样做:
#! /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(喜欢的东西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仍为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
但这意味着必须编写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
_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代码。最重要的是,我们根据数组的值添加位置参数($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
如果你有正确的那只能sudo
是alice
和alice
有权利sudo
的bob
,和bob
作为root
。
因此,在实践中,这并不是真正有用的。使用su
代替sudo
(或使用对目标用户而不是调用方sudo
进行sudo
身份验证的配置)可能更有意义,但这仍意味着您需要了解所有这些人的密码。
ioshcc
吗?您应该只输入一行。