脚本呢?

脚本呢?

我有一个进程在由普通用户运行时需要 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 ( $<) 的特殊变量。但是,如果您尝试相同的操作chownchmod与编译后的可执行文件(来自 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

我有这样的解决方案。

  1. 制作一个 C 包装器,旨在用于 shebang 行。 Setuid 根。
  2. 启动后,包装器将检查作为参数给出的脚本是否为 setuid/setgid。
    • 如果没有 - 删除 root 权限。
    • 如果是 - 更改有效 uid/gid 以反映 setuid/setgid 位。
  3. 检查给定的脚本是否是常规文件 - 如果不是则退出。
  4. 通过“perl -x 脚本”执行 perl。

更严格/有限的版本也可以这样做(和/或):

  • 禁止运行 setuid/setgid root 的脚本,
  • 在污染模式下运行 perl(“perl -x -T script”)。

更高级的版本可以通过 sudo 启动 perl(“exec sudo ... perl -x script”)。

相关内容