euid, ruid, suid

支持 HackTricks

用户标识变量

  • ruid真实用户 ID 表示发起该进程的用户。

  • euid:称为 有效用户 ID,表示系统用来确定进程权限的用户身份。通常情况下,euidruid 相同,除非在执行 SetUID 二进制文件的情况下,euid 采用文件所有者的身份,从而授予特定的操作权限。

  • suid:此 保存用户 ID 在高权限进程(通常以 root 身份运行)需要暂时放弃其权限以执行某些任务时至关重要,之后再恢复其最初的提升状态。

重要说明

非 root 进程只能将其 euid 修改为当前的 ruideuidsuid

理解 set*uid 函数

  • setuid:与最初的假设相反,setuid 主要修改 euid 而不是 ruid。具体而言,对于特权进程,它将 ruideuidsuid 与指定用户(通常是 root)对齐,有效地巩固这些 ID,因为 suid 会覆盖。详细信息可以在 setuid 手册页 中找到。

  • setreuidsetresuid:这些函数允许对 ruideuidsuid 进行细致的调整。然而,它们的能力取决于进程的权限级别。对于非 root 进程,修改仅限于当前的 ruideuidsuid 值。相比之下,root 进程或具有 CAP_SETUID 能力的进程可以为这些 ID 分配任意值。更多信息可以从 setresuid 手册页setreuid 手册页 中获取。

这些功能的设计并不是作为安全机制,而是为了促进预期的操作流程,例如当程序通过更改其有效用户 ID 来采用另一个用户的身份时。

值得注意的是,虽然 setuid 可能是提升到 root 权限的常用方法(因为它将所有 ID 对齐到 root),但区分这些函数对于理解和操控不同场景下的用户 ID 行为至关重要。

Linux 中的程序执行机制

execve 系统调用

  • 功能execve 启动一个程序,由第一个参数决定。它接受两个数组参数,argv 用于参数,envp 用于环境。

  • 行为:它保留调用者的内存空间,但刷新堆栈、堆和数据段。程序的代码被新程序替换。

  • 用户 ID 保持

  • ruideuid 和附加的组 ID 保持不变。

  • 如果新程序设置了 SetUID 位,euid 可能会有细微变化。

  • suid 在执行后从 euid 更新。

  • 文档:详细信息可以在 execve 手册页 中找到。

system 函数

  • 功能:与 execve 不同,system 使用 fork 创建一个子进程,并在该子进程中执行命令,使用 execl

  • 命令执行:通过 sh 执行命令,使用 execl("/bin/sh", "sh", "-c", command, (char *) NULL);

  • 行为:由于 execlexecve 的一种形式,它在新子进程的上下文中以类似方式操作。

  • 文档:进一步的见解可以从 system 手册页 中获取。

带有 SUID 的 bashsh 的行为

  • bash

  • 有一个 -p 选项影响 euidruid 的处理方式。

  • 如果没有 -pbash 会将 euid 设置为 ruid,如果它们最初不同。

  • 有了 -p,初始的 euid 会被保留。

  • 更多细节可以在 bash 手册页 中找到。

  • sh

  • 没有类似于 bash 中的 -p 的机制。

  • 关于用户 ID 的行为没有明确提及,除了在 -i 选项下,强调 euidruid 的相等性保持。

  • 额外信息可在 sh 手册页 中找到。

这些机制在操作上各不相同,为执行和程序之间的转换提供了多种选择,具体细节在用户 ID 的管理和保持方面有所不同。

测试执行中的用户 ID 行为

示例取自 https://0xdf.gitlab.io/2022/05/31/setuid-rabbithole.html#testing-on-jail,查看以获取更多信息

案例 1:使用 setuidsystem

目标:理解 setuidsystembash 作为 sh 结合的效果。

C 代码

#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>

int main(void) {
setuid(1000);
system("id");
return 0;
}

编译和权限:

oxdf@hacky$ gcc a.c -o /mnt/nfsshare/a;
oxdf@hacky$ chmod 4755 /mnt/nfsshare/a
bash-4.2$ $ ./a
uid=99(nobody) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0

分析:

  • ruideuid 初始值分别为 99 (nobody) 和 1000 (frank)。

  • setuid 将两者都对齐到 1000。

  • system 执行 /bin/bash -c id,这是由于 sh 到 bash 的符号链接。

  • bash 在没有 -p 的情况下,将 euid 调整为与 ruid 匹配,导致两者都为 99 (nobody)。

案例 2:使用 setreuid 和 system

C 代码

#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>

int main(void) {
setreuid(1000, 1000);
system("id");
return 0;
}

编译和权限:

oxdf@hacky$ gcc b.c -o /mnt/nfsshare/b; chmod 4755 /mnt/nfsshare/b

执行和结果:

bash-4.2$ $ ./b
uid=1000(frank) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0

分析:

  • setreuid 将 ruid 和 euid 都设置为 1000。

  • system 调用 bash,由于用户 ID 的相等性,保持用户 ID,有效地作为 frank 操作。

案例 3:使用 setuid 和 execve

目标:探索 setuid 和 execve 之间的交互。

#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>

int main(void) {
setuid(1000);
execve("/usr/bin/id", NULL, NULL);
return 0;
}

执行和结果:

bash-4.2$ $ ./c
uid=99(nobody) gid=99(nobody) euid=1000(frank) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0

分析:

  • ruid 保持为 99,但 euid 设置为 1000,符合 setuid 的效果。

C 代码示例 2(调用 Bash):

#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>

int main(void) {
setuid(1000);
execve("/bin/bash", NULL, NULL);
return 0;
}

执行和结果:

bash-4.2$ $ ./d
bash-4.2$ $ id
uid=99(nobody) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0

分析:

  • 尽管 euid 通过 setuid 设置为 1000,bash 由于缺少 -peuid 重置为 ruid (99)。

C 代码示例 3 (使用 bash -p):

#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>

int main(void) {
char *const paramList[10] = {"/bin/bash", "-p", NULL};
setuid(1000);
execve(paramList[0], paramList, NULL);
return 0;
}

执行和结果:

bash-4.2$ $ ./e
bash-4.2$ $ id
uid=99(nobody) gid=99(nobody) euid=100

参考文献

支持 HackTricks

Last updated