在脚本.sh中:
#!/bin/bash
func(){
echo "I am here a func."
}
export -f func
export variable="I am here a variable"
sudo -E bash -c "func ; echo $variable"
输出如下:
bash: func: command not found
I am here a variable
- 为什么?
- 有什么解决方案可以让我通过 sudo 在新生 shell 中调用函数 [在 script.sh 中声明]?
其实我知道一个方法,比如:
#!/bin/bash
func(){
echo "I am here a func."
}
FUNC=$(declare -f func)
export variable="I am here a variable"
sudo -E bash -c "$FUNC; func ; echo $variable"
但我觉得它太重了,因为我必须用这种方式注册每个函数。
答案1
export -f func
当我在环境中看到以下内容后:
$ env | grep func
BASH_FUNC_func%%=() …
Bash 将函数导出为变量,其名称类似于BASH_FUNC_foo%%
,值以()
.我相信以前炮弹休克名称没有任何前缀 ( foo=() …
)。Apple 前缀似乎是__BASH_FUNC<
。无论如何,值都以 开头()
。
sudo
尝试对这些名称以及整个环境非常小心。想象一下,您可以sudo
只使用脚本来运行 Bash 脚本。想象一下该脚本合法地使用cat
.通过导出名为 you 的 shell 函数,cat
可以有效地更改脚本并运行任意代码。
您可以使用自己的$PATH
(和有办法做到这一点;但管理员也有办法不让你这样做)。或者,如果您设法使用$LD_PRELOAD
(不那么容易,请参阅man 5 sudoers
其中提到LD_
)。
让我们回到导出的 shell 函数。首先,当我sudo -V
以 root 身份(sudo sudo -V
作为普通用户)运行时,部分输出如下:
Environment variables to remove:
*=()*
或者可能:
Environment variables to remove:
__BASH_FUNC<*
BASH_FUNC_*
我认为取决于 的版本sudo
。
我可以使用以下行从列表中删除这些模式sudoers
(但不要这样做,请先阅读整个答案):
Defaults env_delete -= "*=()* BASH_FUNC_* __BASH_FUNC<*"
我什至可以将它们添加到“保留”列表中(也不要):
Defaults env_keep += "*=()* BASH_FUNC_* __BASH_FUNC<*"
但它并不一定能让您的导出函数与sudo
.原因是 的模式*=()*
可能是硬编码的,某些版本的sudo
拒绝变量的值以 开头()
。这种行为是进化而来的。可用的解决方案取决于sudo
您拥有的版本。
命令环境
由于环境变量可以影响程序行为,因此
sudoers
提供了一种方法来限制要运行的命令继承用户环境中的哪些变量。 sudoers 有两种不同的方式来处理环境变量。默认情况下,该
env_reset
选项处于启用状态。 [...]HOME
、SHELL
和环境变量根据目标用户进行初始化,并LOGNAME
根据调用用户设置变量。如果或选项允许,则将从调用用户的环境中保留其他变量,例如、和。这实际上是环境变量的白名单。 […]除非名称和值部分均由或匹配,否则将删除以 开头的值的环境变量,因为它们可能被 bash shell 解释为函数。在 1.8.11 版本之前,此类变量始终被删除。USER
SUDO_*
DISPLAY
PATH
TERM
env_check
env_keep
()
env_keep
env_check
但是,如果
env_reset
禁用该选项,则env_check
和env_delete
选项未明确拒绝的任何变量将从调用进程继承。在这种情况下,env_check
行为env_delete
就像黑名单一样。在版本 1.8.21 之前,()
始终删除以 开头的值的环境变量。从版本 1.8.21 开始,使用模式 inenv_delete
来匹配 bash shell 函数。由于不可能将所有潜在危险的环境变量列入黑名单,env_reset
因此鼓励使用默认行为。
env_check
由、env_delete
、 或指定的环境变量env_keep
可能包含一个或多个*
字符,这些字符将匹配零个或多个字符。不支持其他通配符。默认情况下,环境变量按名称匹配。但是,如果模式包含等号 (
=
),则变量名称和值都必须匹配。例如,bash shell 函数可以按如下方式匹配:env_keep += "BASH_FUNC_my_func%%=()*"
如果没有
=()*
后缀,这将不匹配,因为默认情况下不保留 bash shell 函数。
正如你所看到的,env_reset
这很重要。您很可能已启用它。请注意,-E
选项 ( )的全部要点是按需sudo -E
禁用。env_reset
在我的 Kubuntu 18.04.3 LTS 中,我的sudo
版本是 1.8.21。默认情况下,它放置*=()*
在要删除的变量列表中。这一行sudoers
让它停止:
Defaults env_delete -= "*=()*"
然后sudo -E
将保留任何导出的 shell 函数。这是我的主要答案。
我的 Debian 9sudo
版本是 1.8.19。默认情况下,它将BASH_FUNC_*
和放在__BASH_FUNC<*
要删除的变量列表中。删除这些条目不起作用,因为该工具几乎总是拒绝值以()
无论如何开头的变量;这种行为似乎是硬编码的。
该版本的手册提供了一些线索。我找到了一个解决方案:
Defaults env_keep += "BASH_FUNC_func%%=()*"
然后sudo
没有 -E
func
如果导出,将保留这个特定的 shell 函数 ( )。缺点(或者在某些情况下可能是优点):
- 这不适用于
sudo -E
,所以我不能简单地按需保留环境的其余部分; - 我需要显式指定变量的全名,不带通配符;
BASH_FUNC_*%%=()*
,BASH_FUNC_*
否则*=()*
将无法工作。
这种方法在 1.8.21 中也有效。
根据手册,在版本 1.8.11 之前,以 开头的变量()
总是被删除。如果您sudo
已经那么老了,那么除了您在问题(或类似问题)中使用的技巧之外,没有其他解决方案。
笔记:
- 不要
sudoers
像任何其他文件一样编辑;使用visudo
。其他预防措施以防万一:- 以 root ( ) 身份启动一个单独的“备用”shell
sudo -i
。如果您sudoers
在主 shell 中做了一些愚蠢的事情,您仍然可以以 root 身份运行而不依赖该文件。 - 通过 SSH 连接时,请在生成提升的 shell 之前使用
tmux
或。screen
如果您做了一些愚蠢的事情sudoers
并且(意外地)连接失败,提升的 shell 将等待您的常规用户重新连接,而不是退出。 - 首先制作文件的备份副本 (
cp -a
),特别是如果您要进行试验的话。 - 当您认为已完成时,请确保在退出“备用”提升的 shell 之前可以重新获得 root 访问权限。
- 以 root ( ) 身份启动一个单独的“备用”shell
sudo
即使您是唯一的用户,并且强化防范允许 sudoers 运行任意代码的技巧也没有什么意义(因为您sudo
可以自由地运行任意代码,而无需任何技巧,并且您希望这样),请三思而后行你允许太多了。允许sudo
保留任何 shell 功能可能会让您很容易搬起石头砸自己的脚;不一定是现在,而是将来。