权限setuid
位告诉 Linux 使用所有者的有效用户 ID(而不是执行者)来运行程序:
> cat setuid-test.c
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv) {
printf("%d", geteuid());
return 0;
}
> gcc -o setuid-test setuid-test.c
> ./setuid-test
1000
> sudo chown nobody ./setuid-test; sudo chmod +s ./setuid-test
> ./setuid-test
65534
然而,这只适用于可执行文件; shell 脚本忽略 setuid 位:
> cat setuid-test2
#!/bin/bash
id -u
> ./setuid-test2
1000
> sudo chown nobody ./setuid-test2; sudo chmod +s ./setuid-test2
> ./setuid-test2
1000
维基百科说:
由于安全漏洞的可能性增加,许多操作系统在应用于可执行 shell 脚本时会忽略 setuid 属性。
假设我愿意接受这些风险,有什么方法可以告诉 Linux 在 shell 脚本上与在可执行文件上一样对待 setuid 位吗?
如果没有,是否有解决此问题的通用解决方法?我当前的解决方案是添加一个sudoers
条目以允许ALL
以我希望其运行的用户身份运行给定的脚本,以NOPASSWD
避免出现密码提示。这样做的主要缺点是每次我想要执行此操作时都需要输入一个sudoers
条目,并且调用者需要sudo some-script
而不只是some-script
答案1
Linux 忽略所有解释的可执行文件(即以行开头的可执行文件)上的 setuid1 位#!
。这comp.unix.questions 常见问题解答解释了 setuid shell 脚本的安全问题。这些问题有两种:与shebang相关的和与shell相关的;我将在下面详细介绍。
如果您不关心安全性并希望在 Linux 下允许 setuid 脚本,则需要修补内核。从 3.x 内核开始,我认为您需要添加对install_exec_creds
在里面load_script
函数,在调用之前open_exec
,但我还没有测试过。
塞图伊德谢邦
#!
shebang ( ) 的典型实现方式存在固有的竞争条件:
- 内核打开可执行文件,发现它以
#!
. - 内核关闭可执行文件并打开解释器。
- 内核将脚本的路径插入到参数列表中(如
argv[1]
),并执行解释器。
如果此实现允许 setuid 脚本,则攻击者可以通过以下方式调用任意脚本:创建指向现有 setuid 脚本的符号链接,执行该脚本,并在内核执行步骤 1 之后、解释器开始执行之前安排更改该链接。开始其第一个论点。为此原因,大多数 unice 都会忽略 setuid 位当他们检测到一个shebang时。
确保此实现安全的一种方法是内核锁定脚本文件,直到解释器打开它(请注意,这不仅必须防止取消链接或覆盖文件,而且还必须防止重命名路径中的任何目录)。但 Unix 系统往往回避强制锁,而符号链接会使正确的锁功能变得特别困难和具有侵入性。我认为没有人这样做。
一些unix系统(主要是OpenBSD、NetBSD和Mac OS X,所有这些都需要内核设置才能启用)实现安全 setuid shebang使用附加功能:路径引用文件描述符上已打开的文件/dev/fd/N
氮(所以打开大致相当于)。许多 UNIX 系统(包括 Linux)有但没有 setuid 脚本。/dev/fd/N
dup(N)
/dev/fd
- 内核打开可执行文件,发现它以
#!
.假设可执行文件的文件描述符是 3。 - 内核打开解释器。
- 内核插入
/dev/fd/3
参数列表(如argv[1]
),并执行解释器。
Sven Mascheck 的 shebang 页面在unices上有很多关于shebang的信息,包括setuid 支持。
Setuid 解释器
假设您已设法使程序以 root 身份运行,因为您的操作系统支持 setuid shebang,或者因为您使用了本机二进制包装器(例如sudo
)。你打开了安全漏洞吗?或许。这里的问题是不是关于解释程序与编译程序。问题是你是否运行时系统如果以特权执行,则行为安全。
任何动态链接的本机二进制可执行文件都以某种方式由动态加载器(如
/lib/ld.so
),加载程序所需的动态库。在许多unice上,您可以通过环境(LD_LIBRARY_PATH
是环境变量的通用名称)配置动态库的搜索路径,甚至可以将其他库加载到所有执行的二进制文件中(LD_PRELOAD
)。程序的调用者可以通过放置特制的代码(以及其他策略)在该程序的上下文libc.so
中执行任意代码。$LD_LIBRARY_PATH
所有健全的系统都会忽略LD_*
setuid 可执行文件中的变量。在贝壳例如 sh、csh 及其衍生物,环境变量自动成为 shell 参数。通过
PATH
、IFS
等参数,脚本的调用者有很多机会在 shell 脚本的上下文中执行任意代码。如果某些 shell 检测到已使用特权调用脚本,则它们会将这些变量设置为合理的默认值,但我不知道是否存在我信任的任何特定实现。大多数运行时环境(无论是本机、字节码还是解释型)都具有类似的功能。很少有人在 setuid 可执行文件中采取特殊的预防措施,尽管运行本机代码的可执行文件通常不会做任何比动态链接(确实采取预防措施)更奇特的事情。
珀尔是一个值得注意的例外。它明确支持 setuid 脚本以安全的方式。事实上,即使您的操作系统忽略了脚本上的 setuid 位,您的脚本也可以运行 setuid。这是因为 perl 附带了一个 setuid root 帮助程序,它执行必要的检查并以所需的权限重新调用所需脚本的解释器。这在佩尔塞克手动的。过去需要 setuid perl 脚本
#!/usr/bin/suidperl -wT
而不是#!/usr/bin/perl -wT
,但在大多数现代系统上,#!/usr/bin/perl -wT
就足够了。
请注意,使用本机二进制文件包装器本身没有做任何事情来防止这些问题。事实上,它可以使情况更差,因为它可能会阻止您的运行时环境检测到它是使用特权调用的并绕过其运行时可配置性。
本机二进制包装器可以使 shell 脚本安全,如果包装器净化环境。脚本必须注意不要做出太多假设(例如关于当前目录),但这就是事实。您可以使用 sudo 来执行此操作,前提是它已设置为清理环境。将变量列入黑名单很容易出错,因此始终将其列入白名单。使用 sudo,请确保该env_reset
选项已打开、已setenv
关闭,并且env_file
仅env_keep
包含无害变量。
TL、博士:
- Setuid shebang 不安全,但通常被忽视。
- 如果您使用特权运行程序(通过 sudo 或 setuid),请编写本机代码或 perl,或使用清理环境的包装器启动程序(例如带有选项的 sudo
env_reset
)。
^如果您将“setgid”替换为“setuid”,则此讨论同样适用;Linux 内核在脚本上都会忽略它们
答案2
解决此问题的一种方法是从可以使用 setuid 位的程序中调用 shell 脚本。
它类似于 sudo。例如,以下是在 C 程序中完成此操作的方法:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
setuid( 0 ); // you can set it at run time also
system( "/home/pubuntu/setuid-test2.sh" );
return 0;
}
将其保存为 setuid-test2.c。
现在
,在该程序二进制文件上执行 setuid:
su - nobody
[enter password]
chown nobody:nobody a.out
chmod 4755 a.out
现在,您应该能够运行它,并且您将看到您的脚本在没有任何人权限的情况下执行。
但在这里,您也需要对脚本路径进行硬编码,或者将其作为命令行参数传递给上面的 exe。
答案3
我在这艘船上添加了一些脚本前缀:
#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"
请注意,这不使用setuid
而只是执行当前文件sudo
。
答案4
如果由于某种原因sudo
不可用,您可以用 C 编写一个瘦包装脚本:
#include <unistd.h>
int main() {
setuid(0);
execle("/bin/bash","bash","/full/path/to/script",(char*) NULL,(char*) NULL);
}
编译后将其设置setuid
为chmod 4511 wrapper_script
.
这与另一个发布的答案类似,但在干净的环境中运行脚本并显式使用/bin/bash
而不是由 调用的 shell system()
,因此消除了一些潜在的安全漏洞。
请注意,这会完全丢弃环境。如果你想使用一些环境变量而不打开漏洞,你真的只需要使用sudo
.
显然,您要确保脚本本身只能由 root 写入。