从setuid root C程序调用脚本-脚本不以root身份运行

31

我需要以root身份运行一个bash脚本(不能使用无需密码的sudo或su),而在Linux中无法设置脚本的setuid位,因此我考虑从一个可执行文件中调用并使其设置setuid:

$ cat wrapper.c
int main(void)
{
        system("/bin/bash ./should_run_as_root.sh");
}
$ gcc -o wrapper wrapper.c
$ sudo chown root wrapper
$ sudo chmod ug+s wrapper
$ ll wrapper
-rwsr-sr-x 1 root users 6667 2009-02-17 11:11 wrapper
$

这个可以工作 - 即脚本正确运行 - 但是脚本会以执行"./wrapper"的用户身份运行。

为什么?如何正确实现?

谢谢!


3
以下答案的原因,请参阅 man system 和 https://dev59.com/PXNA5IYBdhLWcg3wNa-T。(翻译:The reasoning behind the answers below can be found in man system and at https://dev59.com/PXNA5IYBdhLWcg3wNa-T.) - Stefan Lasiewski
5个回答

49

由于可执行文件上的suid位只改变可执行文件将作为其运行的有效用户ID(EUID),而不是getuid()返回的真实用户ID(RUID)。除了对suid解释脚本(任何以"#!"开头的可执行文件)的限制外,一些像bash这样的shell还会在这种情况下将EUID设置回RUID作为额外的安全措施。因此,在执行脚本之前,您需要在C代码中使用调用setuid(0)

请查看setuidseteuidgetuidgeteuid的手册页面,以了解真实和有效UID的确切语义。

(警告) 当然,这是提到许多Unix系统、shell和解释器中对suid脚本的限制的适当时机,原因是如果脚本在执行时不小心清理其输入和环境状态,它们可能是危险的,并且可以被利用进行安全升级。因此,当您这样做时,请非常小心。尽可能严格地设置对您的脚本和包装器的访问权限,只允许执行您打算执行的特定脚本,并在启动脚本之前在C程序中清除环境,将环境变量(如PATH)设置为仅包含必要的内容,并且没有其他人可写入的目录。


在程序结束时重置原始UID,这样更安全吗? - skinp
如果你的程序在脚本完成后还有其他不需要这个的操作,那么是的。如果你只是返回,那么这并不重要。在典型情况下,脚本包装器将使用exec而不是system(...),所以无论如何,包装器在脚本结束后都不会再运行了。 - Tom Alsberg
@TomAlsberg,您是否了解有哪些源代码/文档可以证实Bash在此“自动重置uid”的行为? - steveyang

7

还有一点需要注意的是,这里的限制来自于bash而不是*nix系统本身。Bash实际上对SUID脚本进行验证,只有EUID为root才会执行它们。如果您使用旧版本的shell,通常可以直接得到所需的结果。例如,sh不会进行此类验证:

$ cat wrapper.c
int main(void)
{
            system("/bin/sh -c whoami");
}

$ ls -l wrapper
-rwsr-sr-x 1 root users 8887 Feb 17 14:15 wrapper
$ ./wrapper
root

使用bash:

$ cat wrapper.c
int main(void)
{
            system("/bin/bash -c whoami");
}

$ ls -l wrapper
-rwsr-sr-x 1 root users 8887 Feb 17 14:18 wrapper
$ ./wrapper
skinp

然而,Tom的答案通常是为SUID root程序创建包装器的方法。


当然,你是正确的。Linux通过检查exec调用中可执行文件的模式来处理限制suid脚本,并且这种限制是一些解释器的额外安全措施。我不确定为什么在写这篇文章时会产生混淆。我已经更正了我的答案。谢谢! - Tom Alsberg
当然,我错过了OP代码中的一点,他实际上并没有使用#!解释器规范来执行脚本,而是直接调用bash,因此内核的限制在这种情况下不适用 - 但是内核对于带有suid文件模式的#!脚本确实有此限制。 - Tom Alsberg
1
不再起作用了。一方面,“man system”说“不要在具有set-user-ID或set-group-ID权限的程序中使用system(),因为...”。另一方面,在Ubuntu 14.04中可以工作,但在Ubuntu 16.04中不能。 - anumi
@anumi 正如我之前提到的,这将取决于 shell。在 Ubuntu 中,/bin/bash 实际上链接到 dash。看起来这在 14.04 和 16.04 之间得到了修复,很可能是在 https://bugs.launchpad.net/ubuntu/+source/dash/+bug/1215660 中。其他发行版/版本上的其他 shell 将具有不同的行为。 - skinp

3
在脚本中添加setuid(0)并编译它。这样做后应该就可以正常工作了。
$ cat wrapper.c 
int main(void) 
{ 
        setuid(0);
        system("/bin/bash ./should_run_as_root.sh"); 
} 
$ gcc -o wrapper wrapper.c 
$ sudo chown root wrapper 
$ sudo chmod ug+s wrapper 
$ ll wrapper 
-rwsr-sr-x 1 root users 6667 2009-02-17 11:11 wrapper 
$ 

2
不要在具有set-user-ID或set-group-ID权限的程序中使用system(),因为某些环境变量可能会被用来破坏系统完整性。相反,应该使用exec(3)函数族,但不包括execlp(3)或execvp(3)。在/bin/sh是bash版本2的系统上,具有set-user-ID或set-group-ID权限的程序实际上无法正常工作,因为bash 2在启动时会放弃特权。(Debian使用修改过的bash,当作为sh调用时不会这样做。) - cateof

1

这些示例非常不安全,只要有一点知识,任何人都可以以setuid用户的身份运行任何程序。

除非您首先清理环境,否则永远不要通过shell执行代码。这里展示的大多数示例都容易受到IFS和PATH在运行之前被设置的攻击。


0
为什么sudo不可行?它可以避免激怒安全漏洞,例如:
bash-3.2$ cat test
#!/bin/bash
echo ima shell script durp durp
bash-3.2$ chmod +x test
bash-3.2$ ./test
heh heh
bash-3.2$ 

由于环境未经适当清洁,例如在这种情况下:
export echo='() { builtin echo heh heh; }'

sudo 在这种情况下进行了净化,或许还有其他一些边缘案例和陷阱,最好不要写入自定义 suid 包装器中。


man sudo:“通过sudo运行shell脚本可能会暴露出与setuid shell脚本在某些操作系统上不安全的同样内核漏洞...”一个标准的包装器可以制作suid脚本,但您知道哪个最好吗?在这种情况下,sudo是不安全的,因为它依赖于内核通过/dev/fd/...传递脚本! - Robert Siemer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接