linux和docker的capabilities介绍
验证环境:centos7 x86/64 内核版本4.19.9 在linux 2.2版本之前,当内核对进程进行权限验证的时候,可以将进程划分为两类:privileged(UID=0)和unprivilege(UID!=0)。其中privileged的进程拥有所有内核权限,而unprivileged则根据如可执行文件的权限(effective UID,effective GID,supplementary group等)进行判断。 基于文件访问的进程权限控制 此时进程执行主要涉及6个id:Real uid/gid,Effective uid/gid/supplementary group,Saved set-user-ID/saved set-group-ID。下面以不同的user id为例进行讲解,group id也是类似的。
[root@localhost ~]# groupadd newGrp1 [root@localhost ~]# groupadd newGrp2 [root@localhost ~]# useradd -u 10000 -g root -G newGrp1,newGrp2 userTest1 [root@localhost ~]# su userTest1 [userTest@localhost root]$ id uid=10000(userTest) gid=0(root) groups=0(root),1001(newGrp1),1)">1002(newGrp2) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
在上一步中创建了一个usetTest1用户,可以在/etc/passwd查看该用户的home目录,为/home/userTest1 userTest1:x:10000:0::/home/userTest1:/bin/bash 为验证SUID的功能,su切换到userTest1,并在/home/userTest1下创建一个空文件,可以看到wr.log仅对用户userTest1开放写权限 [userTest1@localhost ~]# touch wr.log 在/home/userTest1下编译一个小程序,用于查看当前进程的RUID,EUID和SUID,并写入wr.log。可以看到getIds对所有用户开发了可执行权限 #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { uid_t ruid; uid_t euid; uid_t suid; getresuid(&ruid,&euid,&suid); printf("real_user_id=%d,effictive_user_id=%d,saved_user_id=%dn",ruid,euid,suid); uid_t rgid; uid_t egid; uid_t sgid; getresgid(&rgid,&egid,1)">sgid); printf(real_group_id=%d,effictive_group_id=%d,saved_group_id=%dnstream; stream = fopen( wr.log",1)">a+ ); fprintf( stream,%shello ); fclose( stream ); return 0; } total 20 -rwxr-xr-x. 1 userTest1 root 8712 Dec 50 getIds -rw-r--r--. 1 userTest1 root 554 Dec getIds.c -rw-r--r--. 50 wr.log 在userTest1用户下执行getIds,有如下内容,可以看到其UID为10000,跟创建该用户时设置的值是一样的,RUID=EUID;拉起该进程用户所在的group以及该文件所属的group都是root,所以group的数值显示均为0 [userTest1@localhost ~]$ ./getIds real_user_id=10000,effictive_user_id=10000 real_group_id=0,effictive_group_id=0 在同一个host上创建不同组的用户userTest2。 [root@localhost ~]# groupadd -g 20001 newGrp3 [root@localhost home]# useradd -u 10001 -g newGrp3 userTest2 切换到用户userTest2,并进入/home/userTest1(可能需要为该目录添加other的rx权限)下执行getIds,但因为wr.log的用户和组是userTest1:root,而当前用户是userTest2:newGrp3,因此会因为无法打开wr.log出现段错误。同时也可以看到当前进程的RUDI=EUID=10001,即创建userTest2时的UID;RGID=EGID=20001,为创建newGrp3时的GID [userTest2@localhost userTest1]$ ./10001,1)">1000120001,1)">20001 Segmentation fault (core dumped) SUID的作用就是使可执行文件在不同用户下能以文件拥有者的权限去执行。在userTest1用户下为getIds添加SUID,此时getIds文件的权限中user对应的x变为了s [userTest1@localhost ~]$ chmod 4755 getIds [userTest1@localhost ~]$ ll -rwsr-xr-x. 15 Dec 19:02 wr.log 切换到userTest2,执行getIds,此时可以执行成功,RUID没有变,但EUID和SUID变为了userTest1的值,此时EUID被SUID值为了10000。即当前程序使用userTest1的权限(EUID)去写入wr.log,因此不会出错。但使用SUID是有安全风险的,本例中的程序并没有能力影响除了wr.log之外的系统环境,但如果是一个包含很多功能的命令(如mount ip等),对该命令授予使用某个用户的完整权限,很大程度上有权限泄露的风险,因此对文件设置SUID时需要谨慎。 [userTest2@localhost userTest1]$ ./20001
更多关于RUID EUID和SUID的内容参见深刻理解——real user id,effective user id,saved user id in Linux ? 使用capabilities解决上述问题 在linux内核2.2版本之后将基于用户的权限进行了划分,称为capabilities,capabilities是线程相关的,使用时需要在线程上进程设置(完整的capabilities介绍参见capabilities)。那么如何以capabilities解决上述的问题呢?一个简单的办法是改变wr.log的用户和组,这样就不会出现权限问题 对getIds.c做一个小改动增加一行修改wr.log的用户和用户组的操作,其中10001为usetTest2对应的UID,20001为userTest2对应的GID #include <stdio.h>/home/userTest1/wr.log); FILE * ); fclose( stream ); return ; } 编译上述文件,并使用root用户在userTest1目录下设置getIds1拥有修改文件用户和组的权限CAP_CHOWN,+ep代表将该权限添加到capabilities的Effective和Permitted集合中(下面介绍), [root@localhost userTest1]# setcap cap_chown+ep getIds1 在userTest2下执行getIds可以看到可以执行成功,注意到wr.log的用户和组也被修改为了userTest2的用户和组 [userTest2@localhost userTest1]$ ./getIds1 real_user_id=20001 查看getIds的capabilities,可以看到与设置的一样。最终程序能够运行的原理其实是一样的,即程序的EUID和文件的EUID是一样的。 [userTest2@localhost userTest1]$ getcap getIds1
getIds1 = cap_chown+ep
更简单的办法是给chown设置capabilities,这样进程执行的时候会获取chown上的capabilities,这样就可以拥有权限去执行。在host上执行下面命令。切换到userTest2时就可以使用chown命令直接修改用户和组。此处不能通过给bash设置cap_chow capabilities来操作,因此此时是非root用户,bash进程在执行chown命令的时候会丢掉所有capabilities,导致缺少capabilities而无法运行 # setcap cap_chown=eip /bin/chown
? ? ? capabilities介绍
线程可以使用3种方式修改capabilities:
P'(ambient) = (file is privileged) ? 0 : P(ambient) P(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & cap_bset) | P'(ambient) P(effective) = F(effective) ? P'(permitted) : P(ambient) P(inheritable) = P(inheritable) privileged file指设置了capabilities或设置了SUID或SGID的文件,如果SUID或SGID被忽略,则上述转换将不会发生 F(effective):当一个程序以set-user-ID-root运行或者进程的EUID为0,这类程序被称为capability-dumb binary,此时程序运行的文件的effective bit会被内核设置为enable。内核在程序运行时会检查该程序是否获得了
文件的capabilities使用linux 扩展属性来实现(extended attribute,以下简称EA),EA使用命名空间管理,实现方式比较简单,即key-value方式。文件的capabilities保存在EA的security.capability中,security就是一个命名空间。使用setcap给/usr/bin/的ls目录添加一个capabilities,加入ES,IS和PS中。 # setcap cap_net_raw=eip ls
使用getfattr可以导出该文件对应的EA,"-m -"用于导出所有EA,"-e hex"以16进制方式导出EA # getfattr -d -m - -e hex ls # file: security.capability=0x0100000200200000002000000000000000000000 security.selinux=0x73797374656d5f753a6f626a6563745f723a62696e5f743a733000 可以看到有2个EA,security.capability对应文件的capabilities,另外一个“security.selinux”主要被linux的安全模块调用,实现强制访问控制(MAC),当然我们可以是使用text方式解码此命名空间的内容,跟使用ls -Z查看的结果是一样的 # getfattr -d -m - -e text |