Unix 中的设置用户 ID 机制如何工作?

Unix 中的设置用户 ID 机制如何工作?

有人可以解释一下 Unix 中的 set-user-ID 机制吗?这个设计决定背后的理由是什么?它与有效的用户ID机制有何不同?

答案1

您可能知道 UNIX 中文件的正常读、写和执行权限。

然而,在许多应用程序中,这种类型的权限结构(例如,给予给定用户要么具有读取给定文件的完全权限,要么根本没有读取该文件的权限)过于粗略。因此,Unix 包含了另一个权限位,即set-user-IDbit。如果为可执行文件设置了该位,则每当所有者以外的用户执行该文件时,该用户在访问所有者的任何其他文件时将获得所有者的所有文件读/写/执行权限!

要设置文件的设置用户 ID 位,请键入

 chmod u+s filename

确保您也设置了group-other执行权限;如果也有其他组的读取权限那就太好了。所有这些都可以通过一条语句来完成

 chmod 4755 filename

它也称为已保存的 UID。启动的文件具有 Set-UID 位,保存的 UID 将是文件所有者的 UID。否则,保存的 UID 将是真实 UID。

什么是有效uid?

此 UID 用于评估进程执行特定操作的权限。 EUID 可以更改为真实 UID,或者如果 EUID!=0,则可以更改为超级用户 UID。如果EUID=0,则可以更改为任何内容。

例子

此类程序的一个例子是passwd。如果完整列出它,您将看到它具有 Set-UID 位,并且所有者是“root”。当普通用户(例如“mtk”)运行时passwd,它以以下内容开头:

Real-UID = mtk  
Effective-UID = mtk  
Saved-UID = root

参考链接1
参考链接2

答案2

man credentials在这种情况下是一个很好的信息来源。另请参阅此问题所以。历史解释请看这里存档帖子

与其将“设置UID”和“有效UID”称为机制,不如将UID 的整个概念称为机制。各种 UID 存在的理由是特权分离带来的各种麻烦。即使是普通(非特权)用户有时也需要执行只有特权用户才能执行的操作(访问资源)。为了轻松实现这一点,程序可以更改其 UID。其中有 3 种类型:

  • 真实 UID - 拥有进程的 UID

  • 有效 UID - 进程当前运行的 UID - 这决定了进程在任何特定时刻的实际功能。这也是ps用户字段中显示的内容。

  • 保存的设置 UID - 用于在真实 UID 和有效 UID 之间来回切换的占位符

之所以需要最后一个,是因为普通用户可以仅有的在这三个之间切换没有其他的setuid 程序通常需要以某种方式知道加载它的用户是谁(加上真实的 UID 不应该更改,因为这会造成更大的混乱)。

答案3

mtk的解释很好。

passwd示例是权限提升之一——passwd 始终以 root 身份运行,因为它必须更改只有 root 才能更改的文件。这使得 passwd 可执行文件不易出现缓冲区溢出等情况变得很重要,这样聪明的普通用户就可以将其用于非预期用途。

另一个理由是,以与以 root 身份登录时可能使用的方式相同的方式保护用户su— 以便减少或限制您对特定任务的权限,而不是升级它们。例如,如果我有权启动一个守护程序服务,该服务不需要访问我的东西并且拥有自己的东西,这就是它所需要的全部(例如,记录器),运行它 suid 将意味着它只能访问该东西不是我的或其他人的。

请注意,即使未在可执行文件上设置 suid 位,也可以通过编程方式设置 uid但是,这对于升级不起作用。也就是说,如果您是普通用户并编写了一个在某个时刻自行设置 uid 的程序,则该程序无法切换到 root。我相信 Apache 就是这样工作的。它通常由 root 启动,并有一个进程,然后分叉子进程,将 uid 切换到非特权用户(例如“httpd”)。这些子进程是实际的 Web 服务器工作的内容。

答案4

上面 @mtk 的答案有一个小错误,@Nor.Z 在他的评论中指出。

凭据手册页解释了真实用户 ID (RUID)、有效用户 ID (EUID) 和已保存用户 ID (SUID) 的概念。看这里

如果我理解得很好,默认情况下,当一个进程启动时,它的 EUID 和 SUID 被设置为等于该进程的 RUID。对于团体来说也是如此。但是,如果启动进程的二进制文件在其权限中设置了 set-user-ID 位,则 EUIDSUID 将被设置为二进制文件所有者的 SUID,该所有者可能是像 root 这样的特权用户。 RUID 将保持不变。稍后,如果程序在其源代码中更改了 EUID,则可以在需要时将其重新设置回 root 的 UID,因为 root UID 也存储在 SUID 中并且可以从那里获取。下面的示例 C 程序演示了这一点(改编自Youtube 视频):

// filename: uid.c 
#define _GNU_SOURCE

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {

    uid_t ruid, euid, suid;

    // Get the real, effective, and saved user IDs
    if (getresuid(&ruid, &euid, &suid) == -1) {
        perror("getresuid");
        return 1;
    }

    // Display the user IDs
    printf("Real      UID: %d\n", ruid);
    printf("Effective UID: %d\n", euid);
    printf("Saved     UID: %d\n", suid);

    // file /etc/hosts is owned by root, and only root can open it
    // in write mode. Normal/unprovileged users can only open it in read mode.

    /*See if we can open the /etc/hosts file for reading and writing, as the EUID*/
    printf("open: %d\n", open("/etc/hosts", O_RDWR));
    /* access() tests what the RUID can do. We check 'writing' in this case */
    printf("access: %d\n", access("/etc/hosts", W_OK));

    printf("--\n");
    // drop the privileges in the EUID by setting it with the RUID, and re-try
    seteuid(ruid);

    if (getresuid(&ruid, &euid, &suid) == -1) {
        perror("getresuid");
        return 1;
    }

    printf("Real      UID: %d\n", ruid);
    printf("Effective UID: %d\n", euid);
    printf("Saved     UID: %d\n", suid);

    /*See if we can open the /etc/hosts file for reading and writing, as the EUID*/
    printf("open: %d\n", open("/etc/hosts", O_RDWR));
    /* access() tests what the RUID can do. We check 'writing' in this case */
    printf("access: %d\n", access("/etc/hosts", W_OK));

    return 0;
}

使用 编译程序gcc uid.c -o uid并使用 运行它./uid。它将输出:

Real      UID: 1000
Effective UID: 1000
Saved     UID: 1000
open: -1
access: -1
--
Real      UID: 1000
Effective UID: 1000
Saved     UID: 1000
open: -1
access: -1

打开或访问返回代码 -1 表示操作失败。然后将程序的所有权更改为 root,sudo chown root ./uid并使用 设置 set-user-ID 位sudo chmod u+s ./uid。要检查 do并注意二进制所有者权限ls -lA中的“s” :uid

total 20
-rwsrwxr-x 1 root  gamba 16272 dic  6 16:44 uid
-rw-rw-r-- 1 gamba gamba  1192 dic  6 16:17 uid.c

再次运行程序并与上面进行比较:

Real      UID: 1000
Effective UID: 0
Saved     UID: 0
open: 3
access: -1
--
Real      UID: 1000
Effective UID: 1000
Saved     UID: 0
open: -1
access: -1

可以看出,EUIDSUID 被设置为 root(二进制文件的所有者)的 SUID uid!然后使用EUID的打开操作成功(返回码3),但是访问操作失败(返回码-1),因为它仍然使用只能读取文件的RUID(非特权的)/etc/hosts。在“--”标记之后,我们看到如果将 EUID 更改为 RUID,则两个操作都会再次失败。

最后,如果我们将二进制文件的所有权设置uid回普通/非特权用户“gamba”,请再次设置 set-user-ID 位,使用 运行程序sudo,我们将得到输出:

Real      UID: 0
Effective UID: 1000
Saved     UID: 1000
open: -1
access: 0
--
Real      UID: 0
Effective UID: 0
Saved     UID: 1000
access: 0
access: 0

可以看出,本例中的 set-user-ID 位具有将 EUID 设置为非特权用户的效果,从而导致打开操作失败。访问操作成功,因为它是作为真实用户创建的,在本例中是 root(因为我们使用 运行程序sudo)。当我们不想在使用特权用户(如 root)运行程序时错误地更改某些文件时,此行为非常有用,正如@goldilocks 在上面的回答中指出的那样。

相关内容