0%

Linux setuid与权能介绍

Linux下对于一个文件我们熟悉的权限有rwx,表示读写和执行。

今天介绍的是S位,称为setuid位。

setuid

从一个例子开始

ping命令常常用来测试网络是否通畅,我们看看ping的权限是啥:

1
2
$ ll /bin/ping
-rwsr-xr-x 1 root root 55724 6月 15 2016 /bin/ping*

rws!第三个s是啥东西?将其删掉:

1
2
3
4
$ sudo chmod u-s   /bin/ping
$ ll /bin/ping
-rwxr-xr-x 1 root root 55724 6月 15 2016 /bin/ping*

发现就无法ping了,因为没有打开socket的权限。而当我们又将s位给它的时候,又可以ping了。

Setuid位的作用

 Setuid 是让程序在执行过程中拥有文件拥有者的权限。

用passwd命令举例来说,passwd命令也一样设置了setuid位。

为啥这个需要呢?我们知道linux中账号与密码分别存放在 /etc/passwd与 /etc/shadow,而后者的权限如下:

1
2
3
4
5
6
$ which passwd
/usr/bin/passwd
$ ll /usr/bin/passwd
-rwsr-xr-x 1 root root 54256 3月 29 2016 /usr/bin/passwd*
ll /etc/shadow
-rw-r----- 1 root shadow 978 7月 11 11:39 /etc/shadow

当我们使用passwd命令修改自己的密码(非root)的时候,显然是不行的,因为无法对/etc/shadow进行修改。而setuid位此时就可以帮到忙啦!

此时程序暂时得到了文件拥有者root的权限,于是就可以修改shadow文件了。

Linux中设置Setuid的程序

要找出所有设置setuid的程序,只需要使用下面任意一条命令

1
2
find / -perm /u=s
find / -perm -4000

结果如下:

/bin/ping /bin/mount /bin/su /bin/fusermount /bin/ntfs-3g /bin/umount /usr/sbin/pppd /usr/lib/snapd/snap-confine /usr/lib/i386-linux-gnu/ubuntu-app-launch/oom-adjust-setuid-helper /usr/lib/i386-linux-gnu/oxide-qt/chrome-sandbox /usr/lib/openssh/ssh-keysign /usr/lib/eject/dmcrypt-get-device /usr/lib/dbus-1.0/dbus-daemon-launch-helper /usr/lib/policykit-1/polkit-agent-helper-1 /usr/bin/chsh /usr/bin/newgrp /usr/bin/arping /usr/bin/traceroute6.iputils /usr/bin/gpasswd /usr/bin/chfn /usr/bin/passwd /usr/bin/pkexec /usr/bin/sudo

 

权能

在早先的Linux系统中,执行特权操作的时候,内核对于有效的user ID为0 (超级用户root)的时候不进行检查,而对user ID非0的情况进行许可检查。主要是effective UID、effective GID。

Linux 2.2起,Linux有了权能的概念。当执行特权操作的时候,可以查看是否有相应的权能。

权能可以让普通用户完成超级用户可以做的事情。

继续上一个例子

在上一个例子中,我们把setuid位取消了,然后发现ping命令不行了,现在我们用setcap赋予cap_new_raw的权能:

发现可以Ping!并且没有setuid位!

权能的作用

权能相当于规范了你这个程序所拥有的权限。相当于特权的需求的细化。

比如ping命令需要有socket的连接,需要CAP_NET_RAW的权能,其说明如下:

CAP_NET_RAW

  •  Use RAW and PACKET sockets;
  •  bind to any address for transparent proxying.

现在linux的权能有好多,可以见man手册

上面打印出了那么多的setuid位的程序,比如ping 只需要CAP_NET_RAW权能即可

其它部分需要的权能如下:

程序 需要的权能
/bin/ping CAP_NET_RAW
/bin/mount CAP_SYS_ADMIN
/bin/su CAP_DAC_OVERRIDE,CAP_SETGID,CAP_SETUID
/bin/fusermount CAP_SYS_ADMIN
/bin/umount CAP_SYS_ADMIN
/usr/bin/passwd CAP_CHOWN ,CAP_DAC_OVERRIDE ,CAP_FOWNER

 

权能三项与权能变换

在linux中,权能有如下三种:

  • effective:当前有效的权能,执行某特权操作时,操作系统检查cap_effective的对应位是否有效,而不再是检查进程的有效UID是否为0。
  • permitted:当前进程所有能使用的能力,effective包含于permitted
  • inheritable:可以被继承的能力

上面我们设置ping的权能就是设置了effective和permitted。

linux程序可以调用子程序,其权能变换如下:

During an execve, the kernel calculates the new capabilities of the process using the following algorithm:

1
2
3
P'(permitted) = (P(inheritable) & F(inheritable))  (F(permitted) & cap_bset)
P'(effective) = F(effective) ? P'(permitted) : 0
P'(inheritable) = P(inheritable)
  • P:denotes the value of a thread capability set before the execve(2)
  • P':denotes the value of a capability set after the execve(2)
  • F:denotes a file capability set
  • cap_bset:is the value of the capability bounding set (described below).

 

权能变换小程序

实现一个程序其满足以下的功能:

  • 能够永久的删除其子进程的某个权能。
  • 能暂时性的删除其子进程的某个权能。
  • 能让上面被暂时性删除的权能重新获得

以ping为例,我们知道ping需要的权能为cap_net_raw,为了能在execve后执行ping,即execve中的ping有cap_net_raw权能,根据上面的变换规则:

  • 设置/bin/ping权能为cap_net_raw+ei,使得可以继承

    • sudo setcap cap_net_raw+ei /bin/ping
  • 设置当前的进程的权能有cap_net_raw+i权能

此外,我们的程序先进行fork(),在子进程中调用execve执行ping,因为不用fork执行ping后会退出程序。

fork()创建子进程,那么子进程继承父进程的所有能力,因此没必要重新设置fork后的权能。

为了让有更改权能的能力,首先以sudo运行程序,赋予 CAP_SETUID, CAP_SETGID, CAP_SETPCAP权能,然后setuid和gid为1000。

永久删除子进程的权能就移除effective和inheritable、permitted的,暂时性删除就移除effective和inheritable的,恢复暂时性删除的就添加进effective和inheritable。

下面的代码需要安装libcap

  • sudo apt-get install libcap-dev

PS: 这个实验的系统环境为:Linux version 4.8.0-45-generic ([email protected]) (gcc version 6.2.0 20161005 (Ubuntu 6.2.0-5ubuntu12) ) #48-Ubuntu SMP Fri Mar 24 11:44:57 UTC 2017

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#undef _POSIX_SOURCE

extern int errno;

void whoami() {
printf("uid=%i euid=%i gid=%i\n", getuid(), geteuid(), getgid());
}


void listCaps(){
cap_t caps = cap_get_proc();
ssize_t y = 0;
printf("The Process %d was give capabilities %s\n",(int) getpid(), cap_to_text(caps, &y));
fflush(0);
cap_free(caps);
}


void do_ping(){
if(!fork()){
whoami();
listCaps();
execlp("ping","ping","-c 1","baidu.com",NULL);
}
}


void init(){
if(getuid() != 0){
puts("请使用sudo执行本程序");
exit(1);
}

cap_value_t cap_values[] = {CAP_SETUID, CAP_SETGID, CAP_SETPCAP ,CAP_NET_RAW};

cap_t caps = cap_init();
cap_set_flag(caps, CAP_PERMITTED, 4, cap_values, CAP_SET);
cap_set_flag(caps, CAP_EFFECTIVE, 4, cap_values, CAP_SET);
cap_set_proc(caps);
prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
cap_free(caps);

setgid(1000);
setuid(1000);

caps = cap_get_proc();
cap_set_flag(caps, CAP_EFFECTIVE, 4, cap_values, CAP_SET);
cap_set_flag(caps, CAP_INHERITABLE, 4, cap_values, CAP_SET);
cap_set_proc(caps);
cap_free(caps);
whoami();
listCaps();
}

void printInfo(){
puts("\n------权能测试小程序------\n\
请输入如下字符:\n\
1 : 永久的删除其子进程的某个权能\n\
2 : 暂时性的删除其子进程的某个权能\n\
3 : 暂时性删除的权能重新获得\n\
4 : ping -c 1 baidu.com\n\
5 : 查看当前权能\n\
q : 退出\n\
---------------------------\n");
}

int main(int argc, char **argv)
{
init();
char line[128];
printInfo();
while(fgets(line, sizeof(line), stdin)){
char cmd = line[0];

if(cmd == '1' || cmd == '2' || cmd == '3'){
printf("输入要操作的权能名,如cap_net_raw\n");
fgets(line, sizeof(line), stdin);
line[strlen(line) - 1] = '\0';
cap_value_t temp;
if(cap_from_name(line, &temp) < 0){
printf("Error capability name\n");
}
else{
cap_t caps = cap_get_proc();

if(cmd == '3'){
cap_set_flag(caps, CAP_EFFECTIVE, 1, &temp, CAP_SET);
cap_set_flag(caps, CAP_INHERITABLE, 1, &temp, CAP_SET);
}
else{
cap_set_flag(caps, CAP_EFFECTIVE, 1, &temp, CAP_CLEAR);
cap_set_flag(caps, CAP_INHERITABLE, 1, &temp, CAP_CLEAR);
if(cmd == '1')
cap_set_flag(caps, CAP_PERMITTED, 1, &temp, CAP_CLEAR);
}
if(cap_set_proc(caps))
perror("cap_set_proc() ERROR: ");
else
printf("%s %s success\n", (cmd =='3'? "recover":"remove"),line);

cap_free(caps);
}
}
else if(cmd == '4')
do_ping();
else if(cmd == '5')
listCaps();
else if(cmd == 'q')
break;
printInfo();
}
return 0;
}

 

小程序测试

  • gcc test_capability.c -lcap
  • sudo ./a.out

一开始是可以ping的

暂时删除cap_net_raw后不能ping

恢复后又能ping

永久删除后不能ping

注意观察上面打印出的fork后子进程的权能变化。

参考资料

请我喝杯咖啡吧~