为什么 setuid 位的工作不一致?

为什么 setuid 位的工作不一致?

我写了代码:

// a.c
#include <stdlib.h>
int main () {
  system("/bin/sh");
  return 0;
}

使用命令编译:

gcc a.c -o a.out

添加了 setuid 位:

sudo chown root.root a.out
sudo chmod 4755 a.out

在Ubuntu 14.04上,当我以普通用户身份运行时,我获得了root权限。

但在 Ubuntu 16.04 上,我仍然得到当前用户的 shell。

为什么不一样呢?

答案1

改变的是,/bin/sh要么成为bash,要么留下dash,有一个额外的标志-p模仿 bash 的行为。

Bash 要求该-p标志不放弃 setuid 权限,如其中所述手册页:

如果 shell 启动时有效用户(组)id 不等于真实用户(组)id,并且未提供 -p 选项,则不会读取启动文件,shell 函数不会从环境继承,SHELLOPTS 、BASHOPTS、CDPATH 和 GLOBIGNORE 变量,如果它们出现在环境中,将被忽略,并将有效用户 ID 设置为真实用户 ID。如果在调用时提供 -p 选项,则启动行为是相同的,但不会重置有效用户 ID。

之前,dash不关心这一点并允许 setuid 执行(不采取任何措施来阻止它)。但Ubuntu 16.04 的dash联机帮助页有一个附加选项描述,类似于bash

-p priv
如果有效 uid 与 uid 不匹配,则不要尝试重置它。默认情况下不设置此项,以避免错误使用通过 system(3) 或 popen(3) 的 setuid root 程序。

该选项不存在于上游(这可能不会对提议的补丁做出反应*)也不是 Debian 9,但存在于自 2018 年起获得补丁的 Debian buster 中。

"/bin/sh -p"注意:正如 Stéphane Chazelas 所解释的,现在调用已经太晚了,system()因为system()运行任何给定的内容/bin/sh,因此 setuid 已经被删除。德罗伯特的答案在之前的代码中解释了如何处理这个问题system()

*有关历史的更多详细信息这里那里

答案2

可能由于某种原因,shell 在启动过程中将其有效用户 ID 更改回真实用户 ID。您可以通过添加以下内容来验证这一点:

/* needs _GNU_SOURCE; non-Linux users see setregid/setreuid instead */
uid_t euid = geteuid(), egid = getegid();
setresgid(egid, egid, egid);
setresuid(euid, euid, euid);

在你的system(). (实际上,即使在 Linux 上,您可能只需要设置真实的;保存的应该可以不用管。这只是强力调试。根据您设置 id 的原因,您当然可能需要将真实 ID 也保存在某处。)

[此外,如果这不仅仅是学习 setid 工作原理的练习,那么还有很多安全问题需要担心,特别是在调用 shell 时。例如,有许多环境变量会影响 shell 的行为。sudo如果可能的话,更喜欢现有的方法。]

相关内容