浅谈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:
1 |
|
2 |
|
3 |
|
4 |
|
5 | |
6 | int main() |
7 | { |
8 | uid_t ruid, euid, suid; |
9 | getresuid(&ruid, &euid, &suid); //获取当前进程的三个UID的值 |
10 | printf("%d,%d,%d\n", ruid, euid, suid); |
11 | |
12 | printf("%d\n",remove("/usr/test")); |
13 | |
14 | setuid(getuid()); //降权 |
15 | |
16 | getresuid(&ruid, &euid, &suid); |
17 | printf("%d,%d,%d\n", ruid, euid, suid); |
18 | } |
编写完成后在以root用户编译该程序(切记,一定要是root用户),并将该程序赋予SUID:
发现remove函数返回值为0,成功删除test文件。
那么为什么后面还需要加setuid(getuid());
这行代码呢?
我们要先理解setuid()
函数的作用。该函数的定义如下:
1 | SYNOPSIS top |
2 | |
3 | |
4 | |
5 | int setuid(uid_t uid); |
6 | DESCRIPTION top |
7 | setuid() sets the effective user ID of the calling process. If the |
8 | calling process is privileged (more precisely: if the process has the |
9 | CAP_SETUID capability in its user namespace), the real UID and saved |
10 | set-user-ID are also set. |
11 | |
12 | Under Linux, setuid() is implemented like the POSIX version with the |
13 | _POSIX_SAVED_IDS feature. This allows a set-user-ID (other than |
14 | root) program to drop all of its user privileges, do some un- |
15 | privileged work, and then reengage the original effective user ID in |
16 | a secure manner. |
17 | |
18 | If the user is root or the program is set-user-ID-root, special care |
19 | must be taken: setuid() checks the effective user ID of the caller |
20 | and if it is the superuser, all process-related user ID's are set to |
21 | uid. After this has occurred, it is impossible for the program to |
22 | regain root privileges. |
23 | |
24 | Thus, a set-user-ID-root program wishing to temporarily drop root |
25 | privileges, assume the identity of an unprivileged user, and then |
26 | regain root privileges afterward cannot use setuid(). You can |
27 | 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