- capability概述
- 查看当前用户的权限
- 进程的权限
- 在进程内部进行用户切换(进程内调用setuid和setgid)
- 测试内核代码
- 文件权限
- 查看某个文件的权限
- 为某个文件赋权
- 进程创建子进程的时候的权限
在许多文章中都有讲到这部分,本文不做过多解释。自行百度。
capabilities(7) — Linux manual page——官方权威!!!
Linux Capabilities 入门教程:概念篇——米开朗基杨
Linux Capabilities 入门教程:基础实战篇——米开朗基杨
Linux Capabilities 入门教程:进阶实战篇——米开朗基杨
Linux capability详解——弥敦道人-CSDN
在Linux内核2.2之前,为了检查进程权限,将进程区分为两类:特权进程(euid=0)和非特权进程。特权进程(通常为带有suid的程序)可以获取完整的root权限来对系统进行操作。
在linux内核2.2之后引入了capabilities机制,来对root权限进行更加细粒度的划分。如果进程不是特权进程,而且也没有root的有效id,系统就会去检查进程的capabilities,来确认该进程是否有执行特权操作的的权限。
可以通过man capabilities来查看具体的capabilities。
linux一共由5种权限集合。
查看当前用户的权限Permitted ——可以赋予别人的权限。在下文中用大写P简称该权限
Effective ——当前有限的权限(真正实行权限的东西)。在下文中用大写E简称该权限
Inheritable ——可继承的权限。在下文中用大写I简称该权限
Bounding ——边界权限。在下文中用大写B简称该权限
Ambient——环境权限。在下文中用大写A简称该权限
查看/proc/$$/status文件中的Cap部分
普通用户
[10307750@zte.intra@LIN-29076BB8489 ~]$ cat /proc/$$/status | grep Cap CapInh: 0000000000000000 CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 0000003fffffffff CapAmb: 0000000000000000
root用户
[root@LIN-29076BB8489 ~]# cat /proc/$$/status | grep Cap CapInh: 0000000000000000 CapPrm: 0000003fffffffff CapEff: 0000003fffffffff CapBnd: 0000003fffffffff CapAmb: 0000000000000000
CapInh对应上文的I
CapPrm对应上文的P
CapEff对应上文的E
CapBnd对应上文的B
CapAmb对应上文的A
下文中的进程权限用pP、pI、pE、pB、pA来分别对应进程的P、I、E、B、A
首先创建一个进程,sleep进程。sleep 100秒。并且在后台运行。(末尾 &表示后台运行)
[10307750@zte.intra@LIN-29076BB8489 ~]$ sleep 100 & [1] 1968
可以看到该进程的pid为1968,查看该进程的状态,(位置在/proc/"pid"/status)抓取capability部分。
/proc/pid号/status中记录了该pid进程的状态,包括了该进程的权限(capability)
如果不知道进程号,可以使用ps -ef命令来输出所有的进程,然后通过grep命令来搜索想要的信息。
例如本例子中,则可以
[10307750@zte.intra@LIN-29076BB8489 ~]$ ps -ef | head -1; ps -ef | grep sleep UID PID PPID C STIME TTY TIME CMD root 1595 1638 0 10:35 ? 00:00:00 sleep 60 1030775+ 1968 1896 0 10:35 pts/1 00:00:00 sleep 100 1030775+ 2065 59302 0 10:35 ? 00:00:00 sleep 5 1030775+ 2175 1896 0 10:35 pts/1 00:00:00 grep --color=auto sleep
head -1的意思是输出表头,就是UID PID PPID C STIME TTY TIME CMD那一行。
[10307750@zte.intra@LIN-29076BB8489 ~]$ cat /proc/1968/status | grep Cap CapInh: 0000000000000000 CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 0000003fffffffff CapAmb: 0000000000000000
可以看到,该进程的只有B有权限,其他所有集合均没有权限。与该用户的权限是一致的。至于为什么,下文会说。(不是简单的全部复制过来哦~)
我们继续看root用户的。
[10307750@zte.intra@LIN-29076BB8489 ~]$ sudo -i [root@LIN-29076BB8489 ~]# cat /proc/$$/status | grep Cap CapInh: 0000000000000000 CapPrm: 0000003fffffffff CapEff: 0000003fffffffff CapBnd: 0000003fffffffff CapAmb: 0000000000000000
可以看到root用户的权限,只有I和A没有,其他权限都有。与root用户本身的权限是一致的。至于为什么,下文会说。(同样不是简单的全部复制过来哦~)
在进程内部进行用户切换(进程内调用setuid和setgid)当一个进程在执行过程中发生用户切换的时候(在进程的执行代码中,调用了系统调用setuid和setgid)那么进程的capability也会发生相应的变化。
内核代码阅读——一定要收藏啊啊啊!!!
在内核中处理这部分的代码如下:
内核代码位置/security/commoncap.c:1087行
static inline void cap_emulate_setxuid(struct cred *new, const struct cred *old)
{
kuid_t root_uid = make_kuid(old->user_ns, 0);
if ((uid_eq(old->uid, root_uid) ||
uid_eq(old->euid, root_uid) ||
uid_eq(old->suid, root_uid)) //这3个,表示进程原来的用户是root用户
&&
(!uid_eq(new->uid, root_uid) &&
!uid_eq(new->euid, root_uid) &&
!uid_eq(new->suid, root_uid))) //这3个,表示进程限制的用户不是root用户
{
if (!issecure(SECURE_KEEP_CAPS)) { //如果没有设置KEEP_CAPS标志,则清除P和E权限集合
cap_clear(new->cap_permitted);
cap_clear(new->cap_effective);
}
cap_clear(new->cap_ambient); //不管是不是root,统统清除A
}
if (uid_eq(old->euid, root_uid) && !uid_eq(new->euid, root_uid))
cap_clear(new->cap_effective); //曾经是root,现在切换成非root,则清除E
if (!uid_eq(old->euid, root_uid) && uid_eq(new->euid, root_uid))
new->cap_effective = new->cap_permitted; //曾经是非root,现在切换成root,则E=P
}
上述内核代码主要的功能总结如下:
进程以前是root,切换成非root用户以后。如果没有设置KEEP_CAPS标志,则清除E和P权限集。
如果设置了KEEP_CAPS标志,则保留P权限集。
总而言之,只要发生了从root到普通用户切换,E的权限都会被清除掉,P的权限则视是否设置了KKEP_CAPS标志情况而定。
测试内核代码本文例子中使用golang编程语言。
代码文件名:main.go
package main
import (
"fmt"
"syscall"
"time"
)
//SetKeepCaps 表示设置保留权限(capability)标志
func SetKeepCaps() error {
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 1, 0); err != 0 {
return err
}
return nil
}
//ClearKeepCaps 表示设置不保留权限(capability)标志
func ClearKeepCaps() error {
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 0, 0); err != 0 {
return err
}
return nil
}
func main() {
fmt.Println("Hello world!")
fmt.Println("before set, the uid is ", syscall.Getuid())
fmt.Println("before set, the gid is ", syscall.Getgid())
fmt.Println("|***********************************|")
if err := SetKeepCaps(); err != nil { //设置内核代码中的KEEP_CAPS标志
fmt.Println(err)
return
} else {
fmt.Println("* secessfully set keep caps *")
}
fmt.Println("|***********************************|")
syscall.Setgid(1000) //切换组,切换到组id为1000的组上
syscall.Setuid(10001) //切换用户,切换到id为10001的用户上
fmt.Println("after set, the uid is ", syscall.Getuid()) //输出uid
fmt.Println("after set, the gid is ", syscall.Getgid()) //输出gid
time.Sleep(100 * time.Second) //程序挂起100s,在这个时间内,可以新建一个命令行(bash)
//窗口来查看这个进程的权限(capability)
}
上述代码实现的功能:
- 首先,设置KEEP_CAPS标志
- 在程序内部调用setgid和setuid系统调用,完成子进程的用户切换,从root用户切换到普通用户
- 程序休眠100s,在这个时间内,可以用ps命令查找该程序,查看该程序的权限capability
使用方法:
#bash命令 [root@LIN-29076BB8489 ~]# go build main.go
使用go build命令生成可执行文件,文件名为main,没有后缀
然后使用root用户执行main,这个main可执行文件则是从root切换到10001用户上。
[root@LIN-29076BB8489 capability]# ./main Hello world! before set, the uid is 0 before set, the gid is 0 |***********************************| * secessfully set keep caps * |***********************************| after set, the uid is 10001 after set, the gid is 1000
可以看到程序运行正常。ctrl+C退出程序,重新以后台运行的方式运行程序
[root@LIN-29076BB8489 capability]# ./main & [1] 54152 [root@LIN-29076BB8489 capability]# Hello world! before set, the uid is 0 before set, the gid is 0 |***********************************| * secessfully set keep caps * |***********************************| after set, the uid is 10001 after set, the gid is 1000 after Clear, the uid is 10001 after Clear, the gid is 1000
运行以后再敲以下回车!
该程序的pid为54152,去/proc/54152/status 文件中查找权限(Cap)。
[root@LIN-29076BB8489 capability]# cat /proc/54152/status | grep Cap CapInh: 0000000000000000 CapPrm: 0000003fffffffff CapEff: 0000000000000000 CapBnd: 0000003fffffffff CapAmb: 0000000000000000
可以看到,该程序从root用户切换到普通用户以后,权限(capability)只有P和B,E被内核清理了。与内核代码一致。
这里的权限(capability)是进程的权限,保留的是pP和pB。
下面要讲的是文件的权限。
文件权限文件只用E、I、P权限,没有A、B权限!!!
文件只用E、I、P权限,没有A、B权限!!!
文件只用E、I、P权限,没有A、B权限!!!
下文中使用fI、fP、fE来分别表示文件的I、P、E权限
每个文件同样有权限,这些权限决定了某个用户执行该文件时可以进行哪些敏感操作。一般是看可执行文件的权限。
例如,我们的终端就是一个可执行文件,位置是/bin/bash。可以去查看该文件的权限。
[root@LIN-29076BB8489 ~]# getcap /bin/bash [root@LIN-29076BB8489 ~]#
可以看到该文件的权限为空。
查看我们刚刚的main可执行文件的权限:
[root@LIN-29076BB8489 capability]# getcap main [root@LIN-29076BB8489 capability]#
可以看到main可执行文件的文件权限fI、fE、fP也为空
getcap看到的文件权限是普通用户的权限!!!
getcap看到的文件权限是普通用户的权限!!!
getcap看到的文件权限是普通用户的权限!!!
重要的事情说3遍!
对于root用户而言,系统默认为root用户设置的权限为所有权限。即fE、fI、fP均为1。这里的1是指后文进行计算时候使用的1,实际拥有哪些权限还是取决于用户root权限B(边界权限)。(cat /proc/$$/status | grep CapBnd)
root用户下的官方解释
1. If the real or effective user ID of the process is 0 (root),
then the file inheritable and permitted sets are ignored;
instead they are notionally considered to be all ones (i.e.,
all capabilities enabled). (There is one exception to this
behavior, described below in Set-user-ID-root programs that
have file capabilities.)
2. If the effective user ID of the process is 0 (root) or the
file effective bit is in fact enabled, then the file effective
bit is notionally defined to be one (enabled).
为某个文件赋权
以上文的可执行文件main为例。main的普通用户文件权限为空,我们来为main赋予一点权限。
使用命令setcap来进行赋权。
[root@LIN-29076BB8489 capability]# setcap CAP_SYS_ADMIN+eip main [root@LIN-29076BB8489 capability]# getcap main main = cap_sys_admin+eip
命令中的+eip(也可以用=eip)表示,在fE集合中添加cap_sys_admin权限,在fI集合中添加cap_sys_admin权限,在fP集合中添加cap_sys_admin权限
可以看到赋权成功,main可执行文件的E、I、P权限集中都有了cap_sys_admin这个权限。
进程创建子进程的时候的权限当我们在一个进程中创建一个子进程的时候,权限就会发生变化。
例如,我们在



