正确使用setuid位


45

我有一个由普通用户运行时需要root特权的进程。显然,我可以使用“ setuid位”来完成此操作。在POSIX系统上执行此操作的正确方法是什么?

另外,如何使用使用解释器的脚本(bash,perl,python,php等)来做到这一点?

Answers:


53

setuid位可以在一个可执行文件进行设置,以便在运行时,该程序将具有文件,而不是真正的用户的所有者的权限,如果它们是不同的。这是有效 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位,除非在解释器上设置了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($>)和real 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规则并让该用户运行sudo /path/to/script。Sudo剥离了大多数环境变量,并且还允许管理员精细地选择谁可以运行命令以及使用哪些参数。请参阅如何在没有密码提示的情况下以root用户身份运行特定程序?举个例子


1
如果将许多外壳程序称为-privileged外壳程序,则它们的行为会有所不同;如果从负责的脚本中调用,则也可以通过suid root这种方式安全地进行调用。不过,恐怕负责任脚本的概念在现实世界中有点像做梦。无论如何,可能值得一提的是,在提高权限的情况下,尽可能避免所有形式的写入有多么重要……
mikeserv 2014年

@mikeserv我对“ -privileged外壳”和“负责脚本” 的概念不熟悉。您是否要对壳做其他回答?我当然不能答应你;)这样做的理由sudo不多-如果可能的话,这是一个更好的选择-我将其写成通用答案,源于关于php中系统密码身份验证的早期问题。
goldilocks

1
我真的不希望这样做- 负责脚本真的很难 -超越我,大概。但我必须说,我不同意你的分析sudo-我认为sudo作为精神病,因为它假装root。您甚至可以将Shell Globs放入其中sudoerssu就脚本编写目的而言,我认为更好,尽管sudo对实时应用程序也有好处。但是,它们都不像带有#!/bin/ksh -p爆炸线的脚本或suid kshin 那样明确$PATH
mikeserv

尽管此答案确实解决了这个问题,但是setuid脚本是使您能够打开本地特权升级漏洞的诀窍。有一个原因导致无法轻易将脚本设置为setuid。正确的答案是sudo。
mdadm 2014年

我随意编辑您的答案,至少要提及环境变量的巨大漏洞。我不喜欢这样一个规范的答案,因为它在不安全的方法(脚本的setuid包装器)中涉及很多问题。最好是推荐sudo并把扩展讨论留给另一个线程(顺便说一下,我在那儿做了介绍)。
吉尔(Gilles)'所以

11

是的,您可以,但这可能是一个非常糟糕的主意。通常,您不会直接在可执行文件上设置SUID位,而是使用sudo(8)su(1)来执行它(并限制可以执行它的人)

但是请注意,允许普通用户以root用户身份运行程序(尤其是脚本!)存在许多安全问题。他们中的大多数人都必须这样做,除非该程序经过特别且非常仔细地编写以处理该问题,恶意用户才可以使用它轻松获得root shell。

因此,即使您要这样做,最好先清理要作为根用户运行的程序的所有输入,包括其环境,命令行参数,STDIN及其处理的任何文件,来自网络连接的数据它会打开,等等。这很难做,甚至做对也很难。

例如,您可以允许仅使用编辑器编辑某些文件的用户。但是编辑器允许外部帮助程序的命令执行或挂起,从而使用户可以根据自己的意愿执行root shell。或者,您可能正在执行对您来说非常安全的shell脚本,但是用户可以使用修改后的$ IFS或其他环境变量来运行它,从而完全改变其行为。或者可能是应该执行“ ls”的PHP脚本,但是用户在修改$ PATH后运行它,因此使其运行名为“ ls”的shell。或其他数十种安全问题。

如果该程序确实必须以root身份执行其部分工作,则可能最好对其进行修改,使其以普通用户身份运行,而只是通过suid helper程序(在尽可能多地清除后)执行绝对需要root的部分。

请注意,即使您完全信任所有本地用户(例如,您给他们提供了root密码),也不是一个好主意,因为任何一个用户(例如,在线运行PHP脚本,弱密码,或者不管如何)突然让远程攻击者完全破坏了整个计算机。


1
借调。请参阅POSIX程序员系列中的for 的安全调用示例man sh,即使这样做也无法执行command -p env -i ...。毕竟很难做到。尽管如此,如果出于正确的目的正确地完成了该操作,那么suid对于使用有能力的解释器要比使用su或更好,sudo因为任何一个的行为都始终在脚本的控制范围之外(尽管su不太可能将曲线球扔向脚本)少一点疯狂)。捆绑整个软件包是唯一可以肯定的选择-最好确保全部。
mikeserv
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.