我有一个进程在由普通用户运行时需要 root 权限。显然我可以使用“setuid 位”来完成此操作。在 POSIX 系统上执行此操作的正确方法是什么?
另外,如何使用使用解释器(bash、perl、python、php 等)的脚本来执行此操作?
答案1
这设置用户ID位可以在可执行文件上设置,以便在运行时,程序将拥有文件所有者的权限,而不是真实用户的权限(如果它们不同)。这就是之间的区别有效的uid(用户 ID)和真实的uid。
一些常见的实用程序(例如passwd
)由 root 拥有,并且出于必要而以这种方式配置(passwd
需要访问/etc/shadow
只能由 root 读取的权限)。
执行此操作时的最佳策略是立即以超级用户身份执行所需操作,然后降低权限,以便在运行 root 时不太可能发生错误或误用。为此,您需要设置进程的有效的uid 为其真实 uid。在 POSIX C 中:
#define _POSIX_C_SOURCE 200112L // Needed with glibc (e.g., linux).
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
void report (uid_t real) {
printf (
"Real UID: %d Effective UID: %d\n",
real,
geteuid()
);
}
int main (void) {
uid_t real = getuid();
report(real);
seteuid(real);
report(real);
return 0;
}
相关函数,如果它们在 POSIX 系统上常用的话,在大多数高级语言中应该有等效的函数:
getuid()
: 获取真实的uid。geteuid()
: 获取有效的uid。seteuid()
:设置有效的uid。
你不能用最后一个与真实uid不符的方式做任何事情除非在可执行文件上设置了 setuid 位。因此,要尝试这个,请编译gcc test.c -o testuid
.然后,您需要具有特权:
chown root testuid
chmod u+s testuid
最后一个设置 setuid 位。如果您现在./testuid
以普通用户身份运行,您将看到该进程默认以有效 uid 0、root 运行。
脚本呢?
这因平台而异,但在 Linux 上,需要解释器的东西(包括字节码)不能使用 setuid 位,除非它在解释器上设置(这将是非常非常愚蠢的)。下面是一个模仿上面 C 代码的简单 Perl 脚本:
#!/usr/bin/perl
use strict;
use warnings FATAL => qw(all);
print "Real UID: $< Effective UID: $>\n";
$> = $<; # Not an ASCII art greedy face, but genuine perl...
print "Real UID: $< Effective UID: $>\n";
忠实于它的 *nixy 根源,perl 内置了有效 uid ( $>
) 和实际 uid ( $<
) 的特殊变量。但是,如果您尝试相同的操作chown
并chmod
与编译后的可执行文件(来自 C,前面的示例)一起使用,则不会有任何区别。该脚本无法获得权限。
答案是使用 setuid 二进制文件来执行脚本:
#include <stdio.h>
#include <unistd.h>
int main (int argc, char *argv[]) {
if (argc < 2) {
puts("Path to perl script required.");
return 1;
}
const char *perl = "perl";
argv[0] = (char*)perl;
return execv("/usr/bin/perl", argv);
}
gcc --std=c99 whatever.c -o perlsuid
然后编译这个chown root perlsuid && chmod u+s perlsuid
。您现在可以执行任何有效 uid 为 0 的 perl 脚本,无论谁拥有它。
类似的策略也适用于 php、python 等。 但...
# Think hard, very important:
>_< # Genuine ASCII art "Oh tish!" face
拜托拜托不要将这种东西随意放置。 最有可能的是,您实际上希望以脚本的名称进行编译绝对路径,即将其中的所有代码替换main()
为:
const char *args[] = { "perl", "/opt/suid_scripts/whatever.pl" }
return execv("/usr/bin/perl", (char * const*)args);
他们确保/opt/suid_scripts
其中的所有内容对于非 root 用户都是只读的。否则,有人可以用任何东西来交换whatever.pl
。
另外,请注意许多脚本语言允许环境变量改变它们执行脚本的方式。例如,环境变量可能会导致加载调用者提供的库,从而允许调用者以 root 身份执行任意代码。因此,除非您知道解释器和脚本本身对于所有可能的环境变量都是稳健的,不要这样做。
那么我应该做什么呢?
允许非 root 用户以 root 身份运行脚本的更安全方法是添加须藤规则并让用户运行sudo /path/to/script
。Sudo 会删除大多数环境变量,还允许管理员精细地选择谁可以运行该命令以及使用哪些参数。请参阅如何在没有密码提示的情况下以 root 身份运行特定程序?举个例子。
答案2
是的,你可以,但它是可能是一个非常糟糕的主意。通常,您不会直接在可执行文件上设置 SUID 位,而是使用须藤(8)或者苏(1)执行它(并限制谁可以执行它)
但请注意,允许普通用户以 root 身份运行程序(尤其是脚本!)会带来许多安全问题。他们中的大多数人都必须这样做,除非程序是专门且非常仔细地编写的来处理这个问题,否则恶意用户可以使用它轻松获取 root shell。
因此,即使您要这样做,最好首先清理要以 root 身份运行的程序的所有输入,包括其环境、命令行参数、STDIN 及其处理的任何文件、来自网络连接的数据它打开了,等等。这很难做到,更难做得正确。
例如,您可能允许用户仅使用编辑器编辑某些文件。但编辑器允许执行外部帮助程序的命令或暂停命令,从而使用户可以按照自己的意愿执行 root shell 操作。或者,您可能正在执行对您来说看起来非常安全的 shell 脚本,但用户可以使用修改后的 $IFS 或其他环境变量来运行它,从而完全改变它的行为。或者它可能是应该执行“ls”的 PHP 脚本,但用户使用修改的 $PATH 运行它,从而使其运行一个他命名为“ls”的 shell。或者数十个其他安全问题。
如果程序确实必须以 root 身份完成部分工作,那么最好对其进行修改,使其以普通用户身份运行,但只需通过 suid 帮助程序执行(在尽可能清理之后)绝对需要 root 的部分。
请注意,即使您完全信任所有本地用户(例如,您给他们 root 密码),这也是一个坏主意,因为其中任何人的任何无意的安全监督(例如,在线使用 PHP 脚本,或弱密码,或无论如何)突然允许远程攻击者完全破坏整个机器。
答案3
我有这样的解决方案。
- 制作一个 C 包装器,旨在用于 shebang 行。 Setuid 根。
- 启动后,包装器将检查作为参数给出的脚本是否为 setuid/setgid。
- 如果没有 - 删除 root 权限。
- 如果是 - 更改有效 uid/gid 以反映 setuid/setgid 位。
- 检查给定的脚本是否是常规文件 - 如果不是则退出。
- 通过“perl -x 脚本”执行 perl。
更严格/有限的版本也可以这样做(和/或):
- 禁止运行 setuid/setgid root 的脚本,
- 在污染模式下运行 perl(“perl -x -T script”)。
更高级的版本可以通过 sudo 启动 perl(“exec sudo ... perl -x script”)。