用 setuid() 设置实际用户 UID 和有效用户 UID。
用 setgid() 设置实际组 ID 和有效组 ID。
两个函数声明如下:
[C++] 纯文本查看 复制代码 #include <sys/types.h>
#include <unistd.h>
int setuid(uid_t uid);
int seteuid(uid_t euid);
在使用该函数时会遇到以下情况:
1. 若进程有 root 权限,则函数将实际用户 ID、有效用户 ID 设置为参数 uid 。使用 root 运行下面代码:
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
void show_ids(void)
{
printf ("real uid: %d\n", getuid());
printf ("effective uid: %d\n", geteuid());
}
int main(int argc, char *argv[])
{
int uid;
show_ids();
uid = atoi(argv[1]);
if (setuid (uid) < 0)
perror ("setuid error");
show_ids();
return (0);
}
运行输出:# ./setuid.exe 1001
real uid: 0
effective uid: 0
real uid: 1001
effective uid: 1001 由此可见,在 root 下,实际用户 ID 和有效用户 ID 均被设为 setuid() 的参数 uid 的值。
2. 若进程不具有 root 权限,那么普通用户使用 setuid() 时参数 uid 只能是自己的,没有权限设置别的数值,否则返回失败:$ ./setuid.exe 1001
real uid: 1000
effective uid: 1000
setuid error: Operation not permitted
real uid: 1000
effective uid: 1000
由以上可以看出,只有超级用户进程才能更改实际用户 ID 。所以一个非特权用户进程不能通过 setuid 或 seteuid 得到特权用户权限。但是 su 命令却能使一个普通用户变成特权用户。这并不矛盾,因为 su 是一个 "set_uid" 程序。执行一个设置了 "set_uid" 位的程序时,内核将进程的有效用户 ID 设置为文件属主的 ID(root 的 ID)。而内核检查一个进程是否具有访问某权限时,是使用进程的有效用户 ID 来进行检查的。su 程序的文件属主是 root,普通用户运行 su 命令时,su 进程的权限是 root 权限。
Warning:对于调用了 setuid() 函数的程序要格外小心,当进程的有效用户 ID 即 euid 是 root 用户时,如果调用 setuid() 使 euid 为其他非 root 用户,则该进程从此就不具有超级用户权限了。看下面代码:
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <stdlib.h>
void show_ids (void)
{
printf ("The real user ID is: %d\n", getuid());
printf ("The effective user ID is :%d\n", geteuid());
}
int main(void)
{
const char *file = "/root/rootfile3.txt";
show_ids();
if (!unlink (file)) {
printf ("Ok, I am root, and I can delete the file which in /root directory.\n");
system ("echo hello world > /root/rootfile3.txt");
printf ("Now, drop the root privileges.\n");
if (setuid (1000) < 0) {
perror ("setuid");
exit (EXIT_FAILURE);
}
show_ids();
if (unlink (file) < 0) {
printf ("Ok, we have no privilege to delete rootfile.txt.\n");
}
printf ("try to regain root power again...\n");
if (seteuid (0)) {
perror ("seteuid");
show_ids();
exit (EXIT_FAILURE);
}
}
我们使用 root 编译上面的程序,并运行 chmod u+s 给程序添加 suid 位,然后以普通用户来运行它。
在上面程序中,在运行 setuid (1000) 函数时,我们还是具有 root 权限的,所以该函数会设置成功。正是因为有了 root 权限,所以 3 个 ID (真实用户ID,已保存用户ID,有效用户ID)都会被设置为 1000。所以此后程序已经被降权为普通用户,想再 seteuid (0) 已经不可能。
可以这样使用 stuid() 函数:
开始时,某个程序需要 root 权限玩成一些工作,但后续的工作不需要 root 权限。可以将该可执行程序文件设置 set_uid 位,并使得该文件的属主为 root。这样,普通用户执行这个程序时,进程就具有了 root 权限,当不再需要 root 权限时,调用 setuid( getuid() ) 恢复进程的实际用户 ID 和有效用户 ID 为执行该程序的普通用户的 ID 。对于一些提供网络服务的程序,这样做是非常有必要的,否则就可能被攻击者利用,使攻击者控制整个系统。
对于设置了 set_uid 位的可执行程序也要注意,尤其是对那些属主是 root 的更要注意。因为 Linux 系统中 root 用户拥有最高权力。黑客们往往喜欢寻找设置了 set_uid 位的可执行程序的漏洞。这样的程序如果存在缓冲区溢出漏洞,并且该程序是一个网络程序,那么黑客就可以从远程的地方轻松地利用该漏洞获得运行该漏洞程序的主机的 root 权限。即使这样的成不是网络程序,那么也可以使本机上的恶意普通用户提升为 root 权限。 |