有人可以解释一下 Unix 中的 set-user-ID 机制吗?这个设计决定背后的理由是什么?它与有效的用户ID机制有何不同?
答案1
您可能知道 UNIX 中文件的正常读、写和执行权限。
然而,在许多应用程序中,这种类型的权限结构(例如,给予给定用户要么具有读取给定文件的完全权限,要么根本没有读取该文件的权限)过于粗略。因此,Unix 包含了另一个权限位,即set-user-ID
bit。如果为可执行文件设置了该位,则每当所有者以外的用户执行该文件时,该用户在访问所有者的任何其他文件时将获得所有者的所有文件读/写/执行权限!
要设置文件的设置用户 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
答案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 位,则 EUID和SUID 将被设置为二进制文件所有者的 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
可以看出,EUID和SUID 被设置为 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 在上面的回答中指出的那样。