进程凭证
每个进程都有一套用数字表示的
useridentifiers(UIDs)
和group identifiers(GIDs)
,这些ID称为进程的凭证,决定了进程的权限。
UIDs
和GIDs
包含以下几种:
-
实际用户ID:
real user ID(RUID)
和实际组ID:real group ID(RGID)
-
有效用户ID:
effective user ID(EUID)
和有效组ID:effective group ID(EGID)
-
保存set-user-ID:
saved set-user-ID(SUID)
和保存set-grop-ID:saved set-group-ID(SGID)
-
文件系统用户ID:
filesystem user ID(FUID)
和文件系统组ID:filesystem group ID(FGID)
-
附加组ID:
supplementary group IDs
FUID
和FGID
通常与EUID
和EGID
一样,后文不再对其单独叙述。
UIDs
, GIDs
作用
-
RUID(RGID)
: 实际用户(组)ID决定了进程所属的用户和组(可能被进程执行的程序指令改变)。CentOS发行版上,当我们登录bash
(即进入bash
进程提供的命令行终端)时,登录程序会读取/etc/passwd
文件对应用户的第三、第四字段的值作为bash
进程的RUID
和RGID
,我们在bash里面执行的各种命令的RUID
和RGID
都继承自父进程bash
。同样,当创建新进程时,该进程的RUID
,RGID
也继承自其父进程。 -
EUID(EGID)
: 有效用户(组)ID决定了进程执行各种操作时所拥有的权限,通常EUDI
等同于RUID
,但以下两种情况会使其不一样:-
当可执行文件(程序或命令)设置了
set-user-ID
,set-group-ID
,则执行该程序时,进程的有效用户(组)ID会被改变为可执行文件的属主ID(属组ID)。若可执行文件属主、属组ID是0
(比如root),则进程就间接拥有了超级权限。 -
进程执行了
setuid()
之类的系统调用,改变了进程的有效用户ID。
-
-
SUID(SGID)
:save set-user-ID
的初值从EUID
复制而来,通常配合set-user-ID
标志位使用,设置了该标志位的程序启动后,进程的EUID
为程序的属主ID,从而save set-user-ID
的值也为程序属主ID,进程通过seteuid()
等指令可以自由的将EUID
设置为RUID
或save set-user-ID
。当进程需要执行特权操作时,将EUID
设置为save set-user-ID
而获得需要的权限,而执行普通操作时,将EUID
设置为RUID
,降低进程的权限,保证系统安全。
查看进程的UIDs
,GIDs
通过/proc/PID/status
文件可以查看进程此刻的UIDs
和GIDs
,要注意进程的这些ID不是静态不变的,可能被进程执行的程序指令改变(比如执行了setuid()之类的程序,或加载了了另外一个程序,该程序设置了set-user-ID
,set-group-ID
)。
$ cat /proc/$$/status | egrep 'Uid|Gid'
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
上面四个字段分别是:RUID(RGID)
,EUID(EGID)
, SUID(SGID)
, FUID(FGID)
UIDs
相关的系统调用
getuid(), geteuid()
getuid()
获取进程当前的实际用户ID(RUID)
geteuid()
获取进程当前的有效用户ID(EUID)
#include <stdio.h>
#include <unistd.h>
int main() {
printf("GUID: %d, EUID: %d\n", getuid(), geteuid());
return 0;
}
setuid()
, seteuid()
setuid(uid_t uid)
: 根据情况,非特权进程只能修改有效用户ID,而EUID为0的特权进程将RUID
,EUID
,SUID
一并修改,并且这一操作是单向的,即所有特权将消失,再也不能使用setuid(0)
等之类的系统调用将其修改为特权进程。
seteuid(uid_t uid)
: 修改进程的有效ID(EUID)
setuid()
#include <stdio.h>
#include <unistd.h>
int main() {
int num;
/* 若程序的属主ID是0, 并设置了set-user-ID, 即使普通用户调用程序,
* 此时的进程EUID=0, 拥有了特权
*/
printf("1. RUID: %d, EUID: %d\n", getuid(), geteuid());
scanf("%d", &num);
/* 此操作将导致进程的RUID, EUID, SUID均为3000
* 从此进程的特权消失,再也不能恢复
*/
setuid(3000);
printf("2. RUID: %d, EUID: %d\n", getuid(), geteuid());
scanf("%d", &num);
setuid(0);
printf("3. RUID: %d, EUID: %d\n", getuid(), geteuid());
scanf("%d", &num);
return 0;
}
使用root编译程序,并设置set-user-ID
$ gcc test.c -o test
$ chmod u+s ./test
bash RUID为2000
的普通用户执行test
程序进行验证
$ ./test
1. RUID: 2000, EUID: 0
1
2. RUID: 3000, EUID: 3000
2
3. RUID: 3000, EUID: 3000
3
seteuid()
若要想进程不丢失特权,在普通进程和特权进程之间自由切换,则使用seteuid()
#include <stdio.h>
#include <unistd.h>
int main() {
int num;
/* 若程序的属主ID是0, 并设置了set-user-ID, 即使普通用户调用程序,
* 此时的进程EUID=0, 拥有了特权
*/
printf("1. RUID: %d, EUID: %d\n", getuid(), geteuid());
scanf("%d", &num);
/* 此操作只将EUID修改为3000 */
seteuid(3000);
printf("2. RUID: %d, EUID: %d\n", getuid(), geteuid());
scanf("%d", &num);
/* 恢复EUID为0 */
setuid(0);
printf("3. RUID: %d, EUID: %d\n", getuid(), geteuid());
scanf("%d", &num);
return 0;
}
使用root编译程序,并设置set-user-ID
$ gcc test.c -o test
$ chmod u+s ./test
bash RUID为2000
的普通用户执行test
程序进行验证,同时可以查看/proc/PID/status
中记录的进程ID,程序中使用scanf()
就是为了利用等待用户输入暂停进程往下执行。
$ ./test
1. RUID: 2000, EUID: 0
1
2. RUID: 2000, EUID: 3000
2
3. RUID: 2000, EUID: 0
3
进程执行程序导致的EUID
变化
进程加载了新的程序,进程RUID
保持不变,但EUID
有可能会改变,比如该程序设置了set-user-ID
则进程加载该程序将导致自己的EUID
改变为程序的属主ID。
下面举例说明,如下执行main
可执行文件启动了一个进程,接着进程执行了execpl()
系统调用加载了新的程序test
替换已有的后续程序。
main.c
#include <stdio.h>
#include <unistd.h>
int main(void) {
int num;
printf("main-porcess, RUID: %d, EUID: %d\n", getuid(), geteuid());
scanf("%d", &num);
execl("/tmp/test", "test", NULL);
scanf("%d", &num);
}
test.c
#include <stdio.h>
#include <unistd.h>
int main() {
int num;
/* 若程序的属主ID是0, 并设置了set-user-ID, 即使普通用户调用程序,
* 此时的进程EUID=0, 拥有了特权
*/
printf("test-process, RUID: %d, EUID: %d\n", getuid(), geteuid());
scanf("%d", &num);
return 0;
}
使用root编译min.c,test.c程序,并设置test可执行文件set-user-ID
$ gcc main.c -o main
$ gcc test.c -o test
$ chmod u+s ./test
bash RUID为2000
的普通用户执行main
程序进行验证
./main
main-porcess, RUID: 2000, EUID: 2000
bash RUID为0: 此时观察/proc/PID/status
得到同样的结果
$ pstree -p | grep main
|-sshd(814)-+-sshd(2436)---bash(2440)---su(2992)---bash(2993)---main(8857)
$ egrep "Uid" /proc/8857/status
Uid: 2000 2000 2000 2000
bash RUID为2000:输入任意一个数字,让进程加载test
可执行程序,发现EUID
, SUID
均变为0
1
test-process, RUID: 2000, EUID: 0
bash RUID为0: 此时观察/proc/PID/status
得到同样的结果
egrep "Uid" /proc/8857/status
Uid: 2000 0 0 0