我写了代码:
// 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
如果可能的话,更喜欢现有的方法。]