Linux DAC 权限管理详解

   日期:2024-12-26    作者:xwnjl 移动:http://oml01z.riyuangf.com/mobile/quote/49446.html

linux下有多种权限控制的机制,常见的有:DAC(Discretionary Access Control)自主式权限控制和MAC(Mandatory Access Control)强制访问控制。

权限判定过程大概如下:

  • 1、主体拥有自己的凭证来标识自己的身份。在DAC中,主体通常是进程,而凭证是进程对应用的euid和egid。
  • 2、客体拥有属性来标识自己的身份。在DAC中,客体通常是文件,而权限相关属性是文件对应的uid和gid。
  • 3、主体对客体的操作可以称之为行为。DAC的特点就是行为比较简单,行为仅包括R(读)、W(写)、X(执行)这三种。
  • 4、针对"主体对客体发起的行为",查询规则表来进行权限判定。DAC的UGO规则非常简单,把主体分为User、Group、Other三种类型,每种类型拥有自己的RWX mask。

DAC的权限控制策略是非常简洁,行为是简单的RWX三种,主体也很简单的就能被分为UGO三类。这种简洁造也就了DAC检查的开销非常小。

但是凡事都有好有坏,DAC的简洁造就了它的高效,但是过于简洁也让它的权限划分粒度过大,一旦获得了root权限,几乎就是无所不能。在CPU日益高涨的今天,性能开销已经不是问题了,权限的细粒度管理更加重要,所以诞生了MAC。MAC在DAC的基础上,把、、进一步细分。所以它的权限管理粒度更细,但是开销也稍大。

DAC是Linux权限管理的基础机制,我们本文的重点也是DAC。

我们在权限管理的时候,通常做的第一件事就是创建群组(groupadd)、创建用户(useradd)。这些信息存储在以下的三个文件当中,其中最重要的信息就是、、。

每一行都表示的是一个用户的信息;一行有7个段位;每个段位用号分割,例如:

 

useradd 会把新用户的主组设置为 /etc/default/useradd 中 GROUP 变量指定的值,再或者默认是 100。

/etc/group 的内容包括用户组(Group)、用户组口令、GID及该用户组所包含的用户(User),每个用户组一条记录;格式如下:

 

/etc/shadow 文件的内容包括9个段位,每个段位之间用号分割;我们以如下的例子说明:

 
 

我们在讲Linux权限管理的时候经常讲到用户,但是内核中却没有用户这个数据结构。内核中只会识别UID,至于用户名是通过在文件中查找对应关系得到的。

以下是获取uid,并且根据uid查找到对应文件名的实例:

 

权限管理时真正代表用户的是进程,操作文件的也是进程,也就是说用户所拥有的文件访问权限是通过进程来体现的。

用户拥有的权限,是通过进程的credentials成员来描述的。

task_stuct结构体包含credentials的定义:

 

对DAC来说,最重要的就是struct cred中的uid、gid定义:

 
 

仔细看cred结构,其中最令人疑惑的是uid/gid有四种表达方式,这其中的区别在哪里呢?

namemeaningdescriptuidreal UID进程原本的uidsuidsaved UID一个uid缓存
在SUID机制设置euid时,suid同时被设置成euid
在setuid()设置uid时,suid同时被设置成uideuideffective UID有效uid,权限判断时看的就是euid。
初始状态时,uid和euid相同,做一些权限切换时euid可能改变不等于uid了。fsuidUID for VFS opslinux系统中特有的文件操作uid,通常情况下和euid相等。
除非调用setfsuid()设置成不一样

uid的初始状态是在进程创建时,复制父进程的安全凭证:

 

普通进程在初始状态时,uid/suid/euid/fsuid都是相同的。

在linux日常使用时,一般我们使用普通用户来操作,bash进程是普通用户的uid,那么各种操作创建出来的新进程也是普通用户uid。

某些情况下,需要切换到root操作,使用或者命令输入对应密码就能切换到root用户权限。这种权限升级的操作是什么原理呢?

权限的升级依赖于SUID(set-user-id)机制,在文件的UGO策略中除了记录三组用户的 mask位,还针对权限定义了一个补充的位,对应UGO用户分别为。如果文件设置了SUID,那么它在执行的时候,会把进程的权限(euid)设置成文件属主的uid。我们查看文件就是SUID被设置:

 

这个SUID机制就是专门为提升/切换用户权限而设计的,切换用户也必须先提升到root用户才能切换到其他用户。在这类文件被执行后,不需要验证密码,进程的euid被设置成文件属主的uid,如果文件属主是root用户当前进程就有了root权限,同时这时进程的uid和euid也不相等了。

具体的execve()代码解析如下:

 

注意:可以看到SUID的权限是非常大的,如果文件属主是root,不需要验证密码进程权限被提升为root权限。这类文件如果有漏洞的话,就会被攻击获取到root权限,需要特别小心。

在使用进行操作时,并不希望进程一直处于root权限当中,它最终会根据配置切换到适当的权限。

例如的命令执行过程:

  • 1、首先因为的SUID被设置且文件属主为root,execve()执行sudo,进程权限被切换成root权限。
  • 2、接下来sudo进程运行在root权限状态,来验证当前用户密码。
  • 3、如果密码验证成功,则读取配置文件,其中规定了当前用户使用sudo命令时的权限。文件中的配置格式如下,规定了指定用户能切换到哪些用户,能运行哪些命令。
  • 4、如果当前用户允许切换到用户,则执行sudo命令的参数,调用系统调用把当前进程的euid切换成test用户。
  • 5、切换到用户以后,如果允许执行命令,则继续执行命令。

从上述的过程可以看到权限切换的关键途径,一般通过SUID机制来无密码的把权限升级到root,然后在root状态下验证用户密码,再根据配置通过系统调用到权限降级到合适用户。

系统调用可以降级权限,它没有升级的能力。它的几个基本准则是:

  • 1、进程只能设置本进程的uid。
  • 2、如果进程有root权限,那么uid/suid/euid/fsuid可以设置任意值。
  • 3、如果进程没有root权限,那么设置新的uid只能是原uid/suid/euid/fsuid中的一个值,不能是任意值。

这几个系统调用的详细解析过程:

 
 

对DAC模式来说,客体通常是文件。但是进程也是可以作为客体的,还记得进程有两个cred成员,是进程作为主体时的权限凭证,而是进程作为客体时的权限凭证。

对客体文件来说,最重要的属性是文件的属主uid和gid:

 

对应命令查看时的:

需要注意的是进程主体(subject)也是和文件耦合在一起,一是进程的执行代码是从文件中加载的,而是SUID机制能把进程的执行权限修改成文件属主。所以在DAC模型理解时,注意相互之间的概念,避免绕晕。

对DAC模式来说,规则通常比较简单,一般就存储在客体文件inode相关属性中。而MAC模式,规则比较复杂,需要独立的文件来存储。

UGO把操作当前文件的进程分为3种类型:

  • User用户。文件的属主,即主体进程的euid等于客体文件的uid。
  • Group同组用户。即主体进程的egid等于客体文件的gid。
  • Other用户。不满足上述两种条件的其他用户。

每组用户对当前文件的行为,又分为3种权限:

  • r(Read,读取):
    对文件而言,具有读取文件内容的权限;
    对目录来说,具有浏览目录的权限。
  • w(Write,写入):
    对文件而言,具有新增,修改,删除文件内容的权限;
    对目录来说,具有新建,删除,修改,移动目录内文件的权限。
  • x(eXecute,执行):
    对文件而言,具有执行文件的权限;
    对目录了来说该用户具有的权限。

目录权限的总结:

  • 1、目录的只读访问不允许使用cd进入目录,必须要有执行的权限才能进入。
  • 2、只有执行权限只能进入目录,不能看到目录下的内容,要想看到目录下的文件名和目录名,需要可读权限。
  • 3、一个文件能不能被删除,主要看该文件所在的目录对用户是否具有写权限,如果目录对用户没有写权限,则该目录下的所有文件都不能被删除,文件所有者除外
  • 4、目录的w位不设置,即使你拥有目录中某文件的w权限也不能写该文件

文件默认权限:umask值用于设置用户在创建文件时的默认权限,当我们在系统中创建目录或文件时,目录或文件所具有的默认权限就是由umask值决定的。对于root用户,系统默认的umask值是;对于普通用户,系统默认的umask值是。执行umask命令可以查看当前用户的umask值。
umask值一共有4组数字,其中第1组数字用于定义特殊权限,我们一般不予考虑,与一般权限有关的是后3组数字。
默认情况下,对于目录,用户所能拥有的最大权限是;对于文件,用户所能拥有的最大权限是目录的最大权限去掉执行权限,即。因为x执行权限对于目录是必须的,没有执行权限就无法进入目录,而对于文件则不必默认赋予x执行权限。
对于root用户,他的umask值是022。当root用户创建目录时,默认的权限就是用最大权限777去掉相应位置的umask值权限,即对于所有者不必去掉任何权限,对于所属组要去掉w权限,对于其他用户也要去掉w权限,所以目录的默认权限就是;当root用户创建文件时,默认的权限则是用最大权限666去掉相应位置的umask值,即文件的默认权限是。

有了UGO规则,主体进程在RWX客体文件时会做对应的权限规则检查。例如,open一个文件时,几个权限校验的关键节点:

  • 第一步权限检查: 最开始对文件所在路径上每个目录项对应的inode进行执行权限检查.对应代码:path_openat->link_path_walk->may_lookup->inode_permission;
  • 第二步权限检查:如果是新建文件,对文件所在目录项的inode做写和可执行权限检查。对应代码:path_openat->do_last->lookup_open->vfs_create->may_create->inode_permission.
  • 第三步权限检查:真正打开文件之前,对文件所对应的inode做读写权限检查。对应代码:path_openat->do_last->may_open->inode_permission.

inode_permission()最后会调用acl_permission_check()来做UGO规则检查:

 
 

UGO的规则非常简洁和实用,但是在使用的过程中人们发现这个分组粒度实在是太粗了,仅仅3种分组UGO。如果一个用户需要在UGO之外分配一个独有的权限,该怎么操作呢?

在普通权限中,用户对文件只有三种身份,就是属主、属组和其他人;每种用户身份拥有读(read)、写(write)和执行(execute)三种权限。但是在实际工作中,这三种身份实在是不够用。ACL 权限就是为了解决这个问题的。在使用 ACL 权限给用户 st 陚予权限时,st 既不是 /project 目录的属主,也不是属组,仅仅赋予用户 st 针对此目录的 r-x 权限。这有些类似于 Windows 系统中分配权限的方式,单独指定用户并单独分配权限,这样就解决了用户身份不足的问题。ACL是Access Control List(访问控制列表)的缩写,不过在Linux系统中,ACL用于设定用户针对文件的权限,而不是在交换路由器中用来控制数据访问的功能(类似于防火墙)。

ACL主要是以下两条命令:

 

例如:我们要求是目录的属主,权限是;是此目录的属组,组中拥有班级学员和,权限是;其他人的权限是0。这时试听学员来了,她的权限是。我们来看具体的分配命令。

 

ACL规则是UGO规则的补充,相当于扩充了UGO用户组,但是操作的行为RWX还是不能扩充。

ACL规则信息保存在inode当中。例如:Ext4 扩展属性(xattrs)通常存储在磁盘上的一个单独的数据块中,通过inode.i_file_acl*引用。扩展属性的第一应用是存储文件的ACL以及其他安全数据(selinux)。

在上一节acl_permission_check()的分析中我们就已经看到,在User用户匹配失败后就去匹配ACL规则,在ACL规则匹配失败以后才去匹配Group用户。我们看看具体的过程:

 
 

在UGO的使用过程中,人们还发现UGO的另一个弊端,就是行为的划分也是粗粒度的,仅仅三种行为RWX。这样会造成权限过量的情况,如果用户只需要某项行为的权限,但是你只能给他root的rwx权限,那他就拥有了所有其他行为的root rwx权限。这种拥有所有root权限的程序是攻击的首选目标,如果它有漏洞就会造成系统门户大开。

Linux引入了capabilities机制对root行为进行细粒度的控制,实现按需授权,从而减小系统的安全攻击面。

例如,把ping的权限从SUID的root权限,降为普通用户+能力,功能上完全一致,但是安全风险大大降低。

Linux 将传统上与超级用户 root 关联的特权划分为不同的单元,称为 capabilites。Capabilites 作为线程(Linux 并不真正区分进程和线程)的属性存在,每个单元可以独立启用和禁用。如此一来,权限检查的过程就变成了:在执行特权操作时,如果进程的有效身份不是 root,就去检查是否具有该特权操作所对应的 capabilites,并以此决定是否可以进行该特权操作。比如要向进程发送信号(kill()),就得具有 capability CAP_KILL;如果设置系统时间,就得具有 capability CAP_SYS_TIME。

capabilities能力的全集可以参考capabilities man page。

在主体进程的credentials结构体中,定义了本进程的capability能力:

 

和SUID机制一样,在文件执行的时候,把文件的capability和当前进程的capability进行综合:

 

在权限判断时,如果UGO权限匹配拒绝,继续尝试进行capability的匹配:

 
 

综合上述的规则来说,在UGO使用的过程中,大家越来越发现了UGO的弊端就是权限划分的粒度过粗。为了解决这个问题,ACL和Capability从不同的角度尝试解决这个问题:

  • ACL尝试扩充UGO的用户组。把3组扩充成自定义多组。
  • Capability尝试扩充RWX行为。把3组行为扩充成多组行为。

在这之后,selinux综合了ACL和Capability的扩展思路,推出了一套完成的扩充用户组和行为的细粒度权限管理方案。

后续我们再来详细分析selinux的实现。

利用内核的漏洞,直接修改运行进程的euid的值,用来获取root权限。例如:CVE-2017-16995、CVE-2018-1000001、CVE-2016-5195。

这种漏洞的防护分为几步:

  • step 1、在程序execve()执行时记录进程的uid/suid/euid/fsuid初始值。在LSM钩子security_bprm_committed_creds()上记录:
 
  • step 2、在更改uid的合法途径,这几个系统调用(setreuid()/setuid()/setresuid()/setfsuid())中判断uid有没有被非法修改,合法则记录改动。在LSM钩子security_task_fix_setuid()上记录:
 
  • step 3、在各个关键路径上,比较当前的uid值和初始值,如果合法途径不可能做到,则为非法提权:
 
 

CVE-2019-14287,对于sudoer中配置的非root权限用户,使用"sudo #-1"漏洞获得了root权限。

该漏洞的防护,可以在setresuid()系统调用中做pre钩子,如果普通用户传入参数则进行拦截。


特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。


举报收藏 0评论 0
0相关评论
相关最新动态
推荐最新动态
点击排行
{
网站首页  |  关于我们  |  联系方式  |  使用协议  |  隐私政策  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号