浅谈RUID、EUID、SUID
想要实现普通用户在非sudo的情况下,执行需要root权限的函数或者指令,必须要能够理解这三个UID的值。这三个UID分别为实际用户ID(real uid)、有效用户ID(effective uid)、保存的设置用户ID(saved set-user-ID)。
实际用户ID其实就是当前登录系统的用户ID,有效用户ID就是当前进程是以那个用户ID来运行的,而保存的设置用户ID实际上就是有效用户ID的一个副本。
在运行一个进程时,该进程的有效用户ID在一般情况下是实际用户的ID,但是如果该可执行文件具有SUID的权限,那么他的有效用户ID就是这个可执行程序的拥有者。
上述说法可能比较抽象,我们以Linux下的passwd
命令为例,对SUID进行详细的解释。
首先使用ll /usr/bin/passwd
指令查看passwd
命令的权限。并以mylord用户执行passwd
指令。
由
ll /usr/bin/passwd
指令,我们可以看到passwd
这个可执行文件的所有者是root用户,并且根据-rwsr-xr-x
中的s
可以看出这个可执行文件具有自身的SUID权限。当我们以mylord
这个用户执行passwd
指令时,查看进程发现passwd的实际USER却是root,这就是SUID权限。下面对Linux的SUID机制做一个总结:
- (1)、经常运行时能够使用那些资源,不取决于该可执行文件的所属组,而是取决于运行该命令的用户的UID/GID。
- (2)、对于一个root所属的可执行文件,如果对该文件设置了SUID位,则其他所有的普通用户均可以root身份运行该文件,此时,该进程即可获得root所享有的资源。可以简单的理解位让普通用户拥有可以执行“只有root权限才能执行”的特殊权限。
- (3)、SUID的作用是让执行该命令的用户以该命令拥有者的权限去执行,比如普通用户执行passwd时会拥有root的权限。它的标志为:在会出现x的地方出现s(eg:-rwsr-xr-x)。
Linux C中实现特权程序
在Linux C中想要实现特权程序,就需要用到SUID。
首先我们现在/usr/
目录下创建一个test文件并尝试以mylord
用户删除该文件。
发现无法删除test文件,因为我们没有root权限。编写下面的程序test.cpp:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
uid_t ruid, euid, suid;
getresuid(&ruid, &euid, &suid); //获取当前进程的三个UID的值
printf("%d,%d,%d\n", ruid, euid, suid);
printf("%d\n",remove("/usr/test"));
setuid(getuid()); //降权
getresuid(&ruid, &euid, &suid);
printf("%d,%d,%d\n", ruid, euid, suid);
}
编写完成后在以root用户编译该程序(切记,一定要是root用户),并将该程序赋予SUID:
发现remove函数返回值为0,成功删除test文件。
那么为什么后面还需要加
setuid(getuid());
这行代码呢?我们要先理解
setuid()
函数的作用。该函数的定义如下:
SYNOPSIS top
#include <sys/types.h>
#include <unistd.h>
int setuid(uid_t uid);
DESCRIPTION top
setuid() sets the effective user ID of the calling process. If the
calling process is privileged (more precisely: if the process has the
CAP_SETUID capability in its user namespace), the real UID and saved
set-user-ID are also set.
Under Linux, setuid() is implemented like the POSIX version with the
_POSIX_SAVED_IDS feature. This allows a set-user-ID (other than
root) program to drop all of its user privileges, do some un-
privileged work, and then reengage the original effective user ID in
a secure manner.
If the user is root or the program is set-user-ID-root, special care
must be taken: setuid() checks the effective user ID of the caller
and if it is the superuser, all process-related user ID's are set to
uid. After this has occurred, it is impossible for the program to
regain root privileges.
Thus, a set-user-ID-root program wishing to temporarily drop root
privileges, assume the identity of an unprivileged user, and then
regain root privileges afterward cannot use setuid(). You can
accomplish this with seteuid(2).
描述部分翻译一下:
- (1)、若进程具有root特权,则setuid函数将实际用户ID、有效用户ID、以及保存的设置用户ID设置为uid。
- (2)、若进程没有超级用户特权,但是uid等于实际用户ID或保存设置的用户ID,则setuid只将有效用户ID设置为uid。不改变实际用户ID和保存的设置用户ID。
- (3)、如果上面两个条件都不满足,则errno设置为ERERM,并返回出错。
那么显而易见,我们可以理解在执行./test
后发生的所有事情了。
首先在编译过程中,我们以root用户编译该程序,那么可执行文件test
的所属用户为root,之后我们用chmod
指令给该文件赋予了SUID属性,那么在该进程刚开始运行时,实际用户ID为mylord
用户的UID:1000、有效用户ID为root
用户的UID:0、保存的设置用户ID为root
用户的UID:0。在有s属性的可执行文件启动时,该进程的有效用户ID设置为保存的设置用户ID,并且该进程的实际可以享用的资源由有效用户ID决定。因此,该进程具有root
用户所享有的资源并可以成功删除root创建的文件test。之后getuid()
函数获得当前登录的用户ID:1000,并由setuid()
函数赋值,因为该进程有超级用户特权所以将RUID、EUID、SUID全部设置为1000(getuid()
的返回值)。这个语句执行完成之后,该进程被降权,失去了root
用户享有的资源,成为了一个安全的进程。
因此,在做完特权操作后,一定要谨记使用setuid(getuid());
语句对该进程降权以保证系统安全。
参考文献:
https://www.cnblogs.com/bwangel23/archive/2015/01/15/4225818.html
https://www.cnblogs.com/puyangsky/p/5307030.html
https://blog.csdn.net/weixin_34194702/article/details/89999074
http://man7.org/linux/man-pages/man2/setuid.2.html