hacktricks/linux-hardening/privilege-escalation/euid-ruid-suid.md

9 KiB
Raw Blame History

euid, ruid, suid

从零开始学习AWS黑客技术成为专家 htARTEHackTricks AWS Red Team Expert

用户识别变量

  • ruid真实用户ID表示启动进程的用户。
  • euid:被称为有效用户ID,代表系统用于确定进程特权的用户身份。通常情况下,euidruid相同除了像执行SetUID二进制文件这样的情况其中euid会承担文件所有者的身份,从而授予特定的操作权限。
  • suid:这个保存的用户ID在高特权进程通常以root身份运行需要暂时放弃特权以执行某些任务时至关重要然后再恢复其初始的提升状态。

重要说明

一个未以root身份运行的进程只能修改其euid以匹配当前的ruideuidsuid

理解set*uid函数

  • setuid:与最初的假设相反,setuid主要修改euid而不是ruid。特别是对于特权进程,它将ruideuidsuid与指定用户通常是root对齐有效地由于覆盖suid而巩固这些ID。详细见setuid man页面
  • setreuidsetresuid:这些函数允许对ruideuidsuid进行微妙的调整。但是它们的功能取决于进程的特权级别。对于非root进程修改受限于ruideuidsuid的当前值。相反,具有CAP_SETUID能力的root进程或这些进程可以将这些ID分配任意值。更多信息请参阅setresuid man页面setreuid man页面

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

值得注意的是,虽然setuid可能是提升到root的特权的常见选择因为它将所有ID都与root对齐但区分这些函数对于理解和操纵不同情况下的用户ID行为至关重要。

Linux中的程序执行机制

execve系统调用

  • 功能execve启动一个由第一个参数确定的程序。它接受两个数组参数,argv用于参数,envp用于环境。
  • 行为:保留调用者的内存空间,但刷新堆栈、堆和数据段。程序的代码被新程序替换。
  • 用户ID保留
  • ruideuid和附加组ID保持不变。
  • 如果新程序设置了SetUID位euid可能会有微妙的变化。
  • suid在执行后从euid更新。
  • 文档:详细信息请参阅execve man页面

system函数

  • 功能:与execve不同,system使用fork创建一个子进程,并在该子进程中使用execl执行命令。
  • 命令执行:通过execl("/bin/sh", "sh", "-c", command, (char *) NULL);执行命令。
  • 行为:由于execlexecve的一种形式,它的操作类似,但在新的子进程的上下文中进行。
  • 文档:更多见system man页面

bashsh在SUID下的行为

  • bash
  • 具有-p选项影响euidruid的处理方式。
  • 没有-p,如果bash最初设置euidruid不同,则将euid设置为ruid
  • 使用-p,保留初始euid
  • 更多细节请参阅bash man页面
  • sh
  • 不具有类似于bash中的-p机制。
  • 关于用户ID的行为没有明确说明除了在-i选项下,强调保持euidruid的相等性。
  • 更多信息请参阅sh man页面

这些机制在操作上各有不同为执行和在程序之间转换提供了多样的选项特定情况下对用户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。
  • 由于从 sh 到 bash 的符号链接,system 执行 /bin/bash -c id
  • 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由于它们相等有效地作为 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 保持为99euid 被设置为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

分析:

  • 尽管setuideuid设置为1000但由于缺少-pbash会将euid重置为ruid99

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

参考资料

从零开始学习AWS黑客技术 htARTE (HackTricks AWS Red Team Expert)!