Loading [MathJax]/jax/input/TeX/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >linux和docker的capabilities介绍

linux和docker的capabilities介绍

作者头像
charlieroro
发布于 2020-03-23 08:43:42
发布于 2020-03-23 08:43:42
1.6K00
代码可运行
举报
文章被收录于专栏:charlierorocharlieroro
运行总次数:0
代码可运行

验证环境: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也是类似的。

  • supplementary group为user的增补组,例如在添加一个名为usetTest1的user时候,-g执行该user的primary group,-G指定该usetTest1的supplementary groups。使用id命令可以看到“gid=”后面对应usetTest1的primary group,“groups=”后面对应usetTest1的supplementary groups。supplementary groups可以用与DAC验证
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[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),1002(newGrp2) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
  • 进程的RUID为执行该进程的用户ID,该值通常不需要变更;当判断一个进程是否对某个可执行文件有权限时,需要验证EUID,在没有开启SUID功能时,EUID的值等于RUID;SUID主要用于设置EUID。

在上一步中创建了一个usetTest1用户,可以在/etc/passwd查看该用户的home目录,为/home/userTest1

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
userTest1:x:10000:0::/home/userTest1:/bin/bash

为验证SUID的功能,su切换到userTest1,并在/home/userTest1下创建一个空文件,可以看到wr.log仅对用户userTest1开放写权限

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[userTest1@localhost ~]# touch wr.log
[userTest1@localhost ~]# ll-rw-r--r--. 1 userTest1 root   10 Dec 13 18:50 wr.log

在/home/userTest1下编译一个小程序,用于查看当前进程的RUID,EUID和SUID,并写入wr.log。可以看到getIds对所有用户开发了可执行权限

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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=%d\n",ruid,euid,suid);

    uid_t rgid;
    uid_t egid;
    uid_t sgid;

    getresgid(&rgid, &egid, &sgid);
    printf("real_group_id=%d, effictive_group_id=%d, saved_group_id=%d\n",rgid,egid,sgid);

    FILE *stream;
    stream = fopen( "wr.log", "a+" );
    fprintf( stream, "%s", "hello" );
    fclose( stream );

    return 0;
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
total 20
-rwxr-xr-x. 1 userTest1 root 8712 Dec 13 18:50 getIds
-rw-r--r--. 1 userTest1 root  554 Dec 13 18:50 getIds.c
-rw-r--r--. 1 userTest1 root   10 Dec 13 18:50 wr.log

在userTest1用户下执行getIds,有如下内容,可以看到其UID为10000,跟创建该用户时设置的值是一样的,RUID=EUID;拉起该进程用户所在的group以及该文件所属的group都是root,所以group的数值显示均为0

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[userTest1@localhost ~]$ ./getIds
real_user_id=10000, effictive_user_id=10000, saved_user_id=10000
real_group_id=0, effictive_group_id=0, saved_group_id=0

在同一个host上创建不同组的用户userTest2。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[userTest2@localhost userTest1]$ ./getIds
real_user_id=10001, effictive_user_id=10001, saved_user_id=10001
real_group_id=20001, effictive_group_id=20001, saved_group_id=20001
Segmentation fault (core dumped)

SUID的作用就是使可执行文件在不同用户下能以文件拥有者的权限去执行。在userTest1用户下为getIds添加SUID,此时getIds文件的权限中user对应的x变为了s

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[userTest1@localhost ~]$ chmod 4755 getIds
[userTest1@localhost ~]$ ll
-rwsr-xr-x. 1 userTest1 root 8712 Dec 13 18:50 getIds
-rw-r--r--. 1 userTest1 root  554 Dec 13 18:50 getIds.c
-rw-r--r--. 1 userTest1 root   15 Dec 13 19:02 wr.log

切换到userTest2,执行getIds,此时可以执行成功,RUID没有变,但EUID和SUID变为了userTest1的值,此时EUID被SUID值为了10000。即当前程序使用userTest1的权限(EUID)去写入wr.log,因此不会出错。但使用SUID是有安全风险的,本例中的程序并没有能力影响除了wr.log之外的系统环境,但如果是一个包含很多功能的命令(如mount ip等),对该命令授予使用某个用户的完整权限,很大程度上有权限泄露的风险,因此对文件设置SUID时需要谨慎。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[userTest2@localhost userTest1]$ ./getIds
real_user_id=10001, effictive_user_id=10000, saved_user_id=10000
real_group_id=20001, effictive_group_id=20001, saved_group_id=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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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=%d\n",ruid,euid,suid);

    uid_t rgid;
    uid_t egid;
    uid_t sgid;

    getresgid(&rgid, &egid, &sgid);
    printf("real_group_id=%d, effictive_group_id=%d, saved_group_id=%d\n",rgid,egid,sgid);

    chown("/home/userTest1/wr.log", 10001, 20001);
    FILE *stream;
    stream = fopen( "wr.log", "a+" );
    fprintf( stream, "%s", "hello" );
    fclose( stream );

    return 0;
}

编译上述文件,并使用root用户在userTest1目录下设置getIds1拥有修改文件用户和组的权限CAP_CHOWN,+ep代表将该权限添加到capabilities的Effective和Permitted集合中(下面介绍),

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[root@localhost userTest1]# setcap cap_chown+ep getIds1

在userTest2下执行getIds可以看到可以执行成功,注意到wr.log的用户和组也被修改为了userTest2的用户和组

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[userTest2@localhost userTest1]$ ./getIds1
real_user_id=10001, effictive_user_id=10001, saved_user_id=10001
real_group_id=20001, effictive_group_id=20001, saved_group_id=20001

[userTest2@localhost userTest1]$ ll
-rwxrwxrwx. 1 userTest1 root    8712 Dec 13 18:50 getIds
-rwxr-xr-x. 1 root      root    8760 Dec 13 20:08 getIds1-rw-r--r--. 1 userTest2 newGrp3   30 Dec 13 20:09 wr.log

查看getIds的capabilities,可以看到与设置的一样。最终程序能够运行的原理其实是一样的,即程序的EUID和文件的EUID是一样的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[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而无法运行

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# setcap cap_chown=eip /bin/chown

capabilities介绍

  • capabilities可以分为线程capabilities和文件capabilities。
  • 线程capabilities包含以下4个capabilities集合:
  1. Effective:内核进行线程capabilities检查时实际使用到的集合
  2. Inheritable:当程序对应的可执行文件设置了inheritable bit位时,调用execve执行该程序会继承调用者的Inheritable集合,并将其加入到permitted集合。但在非root用户下执行execve时,通常不会保留inheritable 集合,可以考虑使用ambient 集合,当一个程序drop掉一个capabilities时,只能通过execve执行SUID置位的程序或者程序的文件带有该capabilities的方式来获得该capabilities
  3. permitted:effective集合和inheritable集合的超集,限制了它们的范围,因此如果一个capabilities不存在permitted中,是不可以通过cap_set_proc来获取的。当一个线程从permitted集合中丢弃一个capabilities时,只能通过获取程序可执行文件的capabilities或execve一个set-user-ID-root(以root用户权限运行的)程序来获得
  4. ambient :是在内核4.3之后引入的,用于补充Inheritable使用上的缺陷,ambien集合可以使用函数prctl修改。当程序由于SUID(SGID)bit位而转变UID(GID),或执行带有文件capabilities的程序时会导致该集合被清空

线程可以使用3种方式修改capabilities:

  1. fork:子进程使用fork后会继承父进程的capabilities
  2. cap_set_proc:直接调用系统函数修改,但需要CAP_SETPCAP capabilities权限。在内核2.6.33版本之后,禁止程序直接修改非本进程的capabilities,只允许修改调用者自身进程的capabilities(参见capset)。
  3. execve:使用该函数后的capabilities计算方式如下:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)

P:执行前的线程capabilities
P':执行后的线程capabilities
F:文件的capabilities
  • 文件capabilities和线程capabilities共同决定了执行execve之后线程的capabilities。设置文件capabilities需要有CAP_SETFCAP 权限。文件capabilities有如下3种:
  1. Effective:为一个标记位,非capabilities集合。如果设置该标记位,执行execve后的新permitted集合中的capabilities都会添加到effective集合中;反之不会添加(参见上述公式中的:P'(effective) = F(effective) ? P'(permitted) : P'(ambient))。
  2. Inheritable:该集合主要是配合线程capabilities集合使用,具体使用方式参见上述公式
  3. Permitted:同上

文件的capabilities使用linux 扩展属性来实现(extended attribute,以下简称EA),EA使用命名空间管理,实现方式比较简单,即key-value方式。文件的capabilities保存在EA的security.capability中,security就是一个命名空间。使用setcap给/usr/bin/的ls目录添加一个capabilities,加入ES,IS和PS中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# setcap cap_net_raw=eip ls

使用getfattr可以导出该文件对应的EA,"-m -"用于导出所有EA,"-e hex"以16进制方式导出EA

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# getfattr -d -m - -e hex ls
# file: ls
security.capability=0x0100000200200000002000000000000000000000
security.selinux=0x73797374656d5f753a6f626a6563745f723a62696e5f743a733000

可以看到有2个EA,security.capability对应文件的capabilities,另外一个“security.selinux”主要被linux的安全模块调用,实现强制访问控制(MAC),当然我们可以是使用text方式解码此命名空间的内容,跟使用ls -Z查看的结果是一样的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# getfattr -d -m - -e text ls
# file: ls
security.capability="\000\000\000 \000\000\000 \000\000\000\000\000\000\000\000\000"
security.selinux="system_u:object_r:bin_t:s0"
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# ls -Z ls
-rwxr-xr-x. root root system_u:object_r:bin_t:s0       ls

上面使用hex解码出的security.capability值为0x0100000200200000002000000000000000000000,含5个32位的长度,即0x01000002 00200000 00200000 00000000 00000000,对应以下的结构体,注意其为小端序,转化为大端序为0x0200001 00002000 0002000 00000000 00000000

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct vfs_cap_data {
    __le32 magic_etc;        /* Little endian */
    struct {
        __le32 permitted;    /* Little endian */
        __le32 inheritable;  /* Little endian */
    } data[VFS_CAP_U32];
};

第一个32位数值0x0200001的最后一个bit位为1,该bit位就是文件effective的bit位,linux/capability.h中有如下定义(参见代码)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
VFS_CAP_FLAGS_EFFECTIVE 0x000001

在设置文件的capabilities时,内核会根据capabilities的版本(版本的介绍参见capabilities)进行不同处理,同时也会将capabilities 版本号和effective bit位进行位或以及小端序处理(即0x02000000&0x000001=0x0200001),这样就得到了上面的0x0200001。内核代码会根据长度优先处理VFS_CAP_REVISION_2的情况,内核代码参见L501。第三个和第四个32位数据时是一样的,因为添加capabilities时指定了=eip,表示permitted和inheritable,同样也是小端序,2000代表的就是cap_net_raw

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define VFS_CAP_REVISION_2 0x02000000
  • 当执行一个capability-dumb binaries(即设置了文件capabilities,但程序本身并没有调用libcap库管理这些capabilities)时,程序会尝试获取所有的文件capabilities,如果获取失败,则返回错误。这主要是为了防止程序缺少某些capabilities而无法运行

写一个小程序验证上述功能,仅用于输出一句话,编译该文件,输出文件名为hello

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include<stdio.h>
int main()
{
    printf("hello world\n");
    return 0;
}

为hello添加容器中不存在的capabilities ,将该hello使用如下命令拷贝到容器中执行,会出现“sh: ./hello: Operation not permitted”的错误,因此hello程序会尝试获取文件的cap_mac_admin,但因为容器的bounding集合中不存在该capabilities而获取失败。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# setcap cap_mac_admin=eip hello
# docker cp hello c03a:/home
  • 当使用execve执行set-user-ID-root程序时有如下规则:
    • 当执行一个set-user-ID-root程序,或程序的RUD或EUID为0时,则该文件的inheritable集合和permitted集合设置为全1
    • 当执行一个set-user-ID-root程序,或程序的EUID为0时,则该文件的effective bit设置为1
  • 当线程在不同用户之间进行转换时,有如下规则:
    • 如果线程的RUID,EUID以及SUID的一个或多个为0,当这些UID的值都转变为非0时,permitted, effective, ambient集合都会被清空
    • 当线程的EUID从0转变为非0,则程序的effective集合会被清空
    • 当线程的EUID从非0转变为0,则permitted集合会被拷贝到effective集合中如果系统用户的UID从从非0转变为0(setfsuid),则如下capabilities会从effective集合中清除;当系统的EUID从非0转变为0,则permitted集合会被拷贝到effective集合中
    • 如果系统UID 从0转变为非0,则如下capabilities会被清空
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_FOWNER, CAP_FSETID, CAP_LINUX_IMMUTABLE (since Linux 2.6.30), CAP_MAC_OVERRIDE, CAP_MKNOD
  • 当调用capset或cap_set_proc设置程序的capabilities时,必须遵守如下规则:
    • 如果调用者没有CAP_SETPCAP ,则新的inheritable必须是现有inheritable和permitted的合集的子集
    • (Since Linux 2.6.25)新的inheritable必须是现有inheritable和bounding的合集的子集
    • 新的permitted必须是现有permitted的子集
    • 新的effective集合必须是现有permitted集合的子集

这样也看出如果现有进程中的permitted中不存在某个capabilities,那么即使该进程有CAP_SETPCAP权限,也不能设置该capabilities。

使用如下程序验证上述部分功能,test.c用于设置当前线程(进程)的capabilities,调用execve执行test1,中间使用getchar()来中断程序,用于手动查看当前程序的capabilities;test1仅查看execv执行之后的capabilities,编译时需要加上-lcap选项,链接cap库

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
test.c //调用者
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#undef _POSIX_SOURCE
#include <sys/capability.h>

extern int errno;

void listCaps(pid_t pid)
{
    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=%d\n",ruid,euid,suid);

    struct __user_cap_header_struct head={_LINUX_CAPABILITY_VERSION_1, pid};
    struct __user_cap_data_struct data={};

    capget(&head, &data);
    printf("Cap data ES=0x%x, PS=0x%x, IS=0x%x\n", data.effective,data.permitted, data.inheritable);
}

int main(int argc, char **argv)
{
       int error;
        int stat;
        pid_t parentPid = getpid();
        cap_t caps = cap_init();

        cap_value_t capList[] ={ CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_SETPCAP,CAP_SETFCAP } ;
        unsigned num_caps = 4;
        cap_set_flag(caps, CAP_EFFECTIVE, num_caps, capList, CAP_SET);
        cap_set_flag(caps, CAP_INHERITABLE, num_caps, capList, CAP_SET);
        cap_set_flag(caps, CAP_PERMITTED, num_caps, capList, CAP_SET);

        if (cap_set_proc(caps)) {
                perror("capset()");

                return EXIT_FAILURE;
        }
        listCaps(parentPid);
        getchar();
        char *argv2[]={"test1",NULL};
        char *envp[]={0,NULL};
        error=execve("test1",argv2,envp);
        if(0 != error){
            printf("error=%d\n",error);
        }
        parentPid = getpid();

        listCaps(parentPid);

        cap_free(caps);
        return 0;
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
test1.c //被调用者
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#undef _POSIX_SOURCE
#include <sys/capability.h>

extern int errno;

void listCaps(pid_t pid)
{
    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=%d\n",ruid,euid,suid);

    struct __user_cap_header_struct head={_LINUX_CAPABILITY_VERSION_1, pid};
    struct __user_cap_data_struct data={};

    capget(&head, &data);
    printf("Cap data ES=0x%x, PS=0x%x, IS=0x%x\n", data.effective,data.permitted, data.inheritable);
}

int main(int argc, char **argv)
{
    int stat;
    pid_t parentPid = getpid();
    listCaps(parentPid);
    return 0;
}

  上述代码编译方式如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
首先下载libcap源码

wget https://git.kernel.org/pub/scm/libs/libcap/libcap.git/snapshot/libcap-2.26.tar.gz
为验证方便,将上述文件放在在libcap-2.26/libcap下编译
在libcap.h中引用了一个名为cap_names.h的头文件,但libcap目录下不存在,可以直接从这里拷贝

使用docker namespace中user namespace中的方式创建一个unprivileged类型的docker容器(有独立的user namespace)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
docker run -itd centos:latest /bin/sh

在host上找到对应该进程的PID,可以在/proc/$PID/task/TID/status中查看进程的capabilities信息,截图如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CapInh: 00000000a80625fb
CapPrm: 00000000a80625fb
CapEff: 00000000a80625fb
CapBnd: 00000000a80625fb

可以使用capsh来解析上述值

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# capsh --decode=00000000a80625fb
0x00000000a80625fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_rawio,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap

当前也可以通过容器启动进程/bin/sh映射到host上的pid查看对应的capabilities

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# getpcaps 5318
Capabilities for `5318': = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_rawio,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip

将编译好的test和test1拷贝到创建的容器中,当前用户ID如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sh-4.2# id
uid=0(root) gid=0(root) groups=0(root)

执行./test可以看到如下信息,当前进程(执行execev之前)的capabilities如下,由于进程此时由于getchar()中断,使用上述方式查看当前进程的信息如下(虽然该进程的所有uid为0,但其并不是一个真正系统级别的UID,其UID经过了user namespace的映射,映射到/etc/subuid)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sh-4.2# ./test
real_user_id=0, effictive_user_id=0, saved_user_id=0
Cap data ES=0x80000103, PS=0x80000103, IS=0x80000103
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CapInh: 0000000080000103
CapPrm: 0000000080000103
CapEff: 0000000080000103
CapBnd: 00000000a80425fb
CapAmb: 0000000000000000

回车,看到完整的信息如下,虽然test1没有设置文件capabilities,但由于test1的uid都是0,仍然会部分遵守上述提到的"当执行一个set-user-ID-root程序,或程序进程的RUD或EUID为0时,则该文件的inheritable集合和permitted集合设置为全1"的规则(没有全部继承是受bounding集合的限制)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sh-4.2# ./test
real_user_id=0, effictive_user_id=0, saved_user_id=0
Cap data ES=0x80000103, PS=0x80000103, IS=0x80000103

real_user_id=0, effictive_user_id=0, saved_user_id=0
Cap data ES=0xa80425fb, PS=0xa80425fb, IS=0x80000103
  • 当执行一个set-user-ID-root程序,或程序进程的RUD或EUID为0时,则该文件的inheritable集合和permitted集合设置为全1
  • 当执行一个set-user-ID-root程序,或程序进程的EUID为0时,则该文件的effective bit设置为1

在容器中创建一个新的用户,其用户和组都是captest,并修改test和test1的用户和组

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
useradd captest
chown captest:captest test*
chmod 777 test*

切换到captest用户下执行./test会返回”capset(): Operation not permitted“的错误,查看该用户的进程

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[captest@78d4c79e2648 home]$ ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
captest     204    203  0 22:37 pts/3    00:00:00 bash

查看上述bash进程的capabilities信息如下,可以看到effective集合被清空了,这样就导致bash下的子进程的effective集合也是空,没有权限执行。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CapInh: 00000000a80425fb
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 00000000a80425fb
CapAmb: 0000000000000000

bash进程的父进程为“su captest”,其EUID为root,即0,该进程会执行bash命令,即EUID从0 转变为非0。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
root      203    186  0 21:53 pts/1    00:00:00 su captest
captest   204    203  0 21:53 pts/1    00:00:00 bash

上述现象符合如下规则

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
如果程序的RUIDEUID以及SUID的一个或多个为0,当这些UID的值都转变为非0时,permitted, effective, ambient集合都会被清空
当程序的EUID0转变为非0,则程序的effective集合会被清空

在captest用户下执行./test1,同样也可以看到,由于udi全部非0,ES和PS都会清空,但IS没有变

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[captest@78d4c79e2648 home]$ ./test1
real_user_id=1001, effictive_user_id=1001, saved_user_id=1001
Cap data ES=0x0, PS=0x0, IS=0xa80425fb

在内核4.14版本的时候引入了一个Namespaced file capabilities的概念,主要用于解决VFS_CAP_REVISION_2下file capabilities无法在不同user namespace下隔离的问题。在VFS_CAP_REVISION_3版本之前,一个CAP_SETFCAP进程可以在获取file capabilities权限的时候,并不关心该进程所在的user namespace,如果一个进程拥有CAP_SETFCAP的权限,那它可以通过设置并执行多个file capabilities来获取原来没有的capabilities权限,这样就导致进程权限完全不受user namespace的限制且毫无意义(既然自己可以设置capabilities并通过exec获得,那文件自身拥有的capabilities就没用了)。因此在4.14版本新增了一个rootid的变量,rootid是指新创建的命名空间中的UID 0对应初始user namespace的UID的值,即映射到host主机上的user ID的值(/etc/subuid)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct vfs_ns_cap_data {
    __le32 magic_etc;
    struct {
        __le32 permitted;    /* Little endian */
        __le32 inheritable;  /* Little endian */
    } data[VFS_CAP_U32];
    __le32 rootid;
};

TIPS

  • capabilities只是给了线程执行某项功能的能力,但线程是否能调用另外的程序,需要看当前线程的EUID是否与文件的EUID对应,且能获取该文件的所有capabilities(如果文件设置了capabilities)
  • execve与fork不一样,它并不会创建一个子进程,execve执行程序的时候会对capabilities进行重新计算,如果此时的程序uid不为0或文件capabilities为空,则该程序会失去所有的capabilities。
  • 不要轻易给一个线程或文件设置CAP_SYS_ADMIN权限,CAP_SYS_ADMIN的权限类似root
  • 在容器中给文件设置capabilities时可能会出现“Operation not permitted”的错误,可以查看host的系统日志,centos上一般时SElinux功能没有关闭导致的,执行setenforce 0即可
  • capabilities中定义了各个capabilities的值,但要注意这些值代表的是2的幂次方,如“#define CAP_SETPCAP 8”,则CAP_SETPCAP 的值为2^8=256
  • docker可以在run的时候使用--cap-add为容器的初始进程添加capabilities,--cap-drop移除capabilities
  • 使用CAP_SETFCAP可以为文件设置任意capabilities,使用CAP_SETPCAP则需要按照一定的规定添加(见上文)
  • 一个线程的capabilities集合与自身设置和文件capabilities设置相关,同时也受uid的限制。
  • 切换系统用户其实也是执行一个进程(sh或bash)

参考:

http://man7.org/linux/man-pages/man7/capabilities.7.html

https://raesene.github.io/blog/2017/08/27/Linux-capabilities-and-when-to-drop-all/

https://review.lineageos.org/c/LineageOS/android_kernel_samsung_apq8084/+/192902/6

https://github.com/riyazdf/dockercon-workshop/tree/master/capabilities

https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities

https://s3hh.wordpress.com/2017/09/20/namespaced-file-capabilities/

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-12-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Linux Capabilities 入门:让普通进程获得 root 的洪荒之力
Linux 是一种安全的操作系统,它把所有的系统权限都赋予了一个单一的 root 用户,只给普通用户保留有限的权限。root 用户拥有超级管理员权限,可以安装软件、允许某些服务、管理用户等。
米开朗基杨
2019/10/29
9.9K0
Linux Capabilities 入门:让普通进程获得 root 的洪荒之力
Linux Capabilities 入门:如何管理文件的 capabilities?
上篇文章介绍了 Linux capabilities 的诞生背景和基本原理,本文将会通过具体的示例来展示如何查看和设置文件的 capabilities。
米开朗基杨
2019/11/14
4.5K0
在Kubernetes中配置Container Capabilities
实际上这是配置对应的容器的 Capabilities,在我们使用 docker run 的时候可以通过 --cap-add 和 --cap-drop 命令来给容器添加 LinuxCapabilities。对于大部分同学可能又要疑问 LinuxCapabilities 是什么呢?
CNCF
2019/12/04
4.4K0
Linux Capabilities 与容器的水乳交融
更多奇技淫巧欢迎订阅博客:https://fuckcloudnative.io 前言 该系列文章总共分为三篇: ?Linux Capabilities 入门教程:概念篇 ?Linux Capabili
米开朗基杨
2020/10/30
2.1K0
Linux Capabilities 与容器的水乳交融
[kernel] 带着问题看源码 —— setreuid 何时更新 saved-set-uid (SUID)
在写《[apue] 进程控制那些事儿》/"进程创建"/"更改进程用户 ID 和组 ID"一节时,发现 setreuid 更新实际用户 ID (RUID) 或有效用户 ID (EUID) 时,保存的设置用户 ID (saved set-user-id SUID) 只会随 EUID 变更,并不像 man 上说的会随 RUID 变更 (man setreuid):
海海
2024/04/11
1990
[kernel] 带着问题看源码 —— setreuid 何时更新 saved-set-uid (SUID)
让wireshark以非root权限运行背后的linux Capabilities(简介)
Linux是一种安全操作系统,它给普通用户尽可能低的权限,而把全部的系统权限赋予一个单一的帐户–root。root帐户用来管理系统、安装软件、管理帐户、运行某些服务、安装/卸载文件系统、管理用户、安装软件等。另外,普通用户的很多操作也需要root权限,这通过setuid实现。
用户1879329
2023/02/27
2.3K0
Linux内核源代码情景分析-访问权限与文件安全性
在Linux内核源代码情景分析-从路径名到目标节点,一文中path_walk代码中,err = permission(inode, MAY_EXEC)当前进程是否可以访问这个节点,代码如下: int permission(struct inode * inode,int mask) { if (inode->i_op && inode->i_op->permission) { int retval; lock_kernel(); retval = inode->i_op->permission(ino
小小科
2018/05/04
2.8K0
Linux内核源代码情景分析-访问权限与文件安全性
谈一谈Linux与suid提权
前几天我在代码审计知识星球里发表了一个介绍nmap利用interactive模式提权的帖子:
phith0n
2020/10/15
2K0
[apue] 进程控制那些事儿
在介绍进程的创建、启动与终止之前,首先了解一下进程的唯一标识——进程 ID,它是一个非负整数,在系统范围内唯一,不过这种唯一是相对的,当一个进程消亡后,它的 ID 可能被重用。不过大多数 Unix 系统实现延迟重用算法,防止将新进程误认为是使用同一 ID 的某个已终止的进程,下面这个例子展示了这一点:
海海
2024/03/30
4730
[apue] 进程控制那些事儿
linux系统编程之进程(一):进程基本概述
s1mba
2018/01/03
1.7K0
linux系统编程之进程(一):进程基本概述
Linux 权限控制的基本原理
以下是对用户和组信息的举例。 /etc/shadow 中的口令信息为加密存储,不举例。
小小科
2018/08/17
1.3K0
Linux 权限控制的基本原理
Linux下suid提权利用
通常来说,Linux运行一个程序,是使用当前运行这个程序的用户权限,这当然是合理的。但是有一些程序比较特殊,比如我们常用的ping命令。
零度安全
2020/08/05
2.7K0
Linux下suid提权利用
Android/Linux Root 的那些事儿
玩过安卓的朋友应该都对 root 这个名词不陌生,曾几何时,一台 root 过的手机是发烧友标配;对于开发者来说,root 后的手机是黑灰产外挂的温床,是想要极力避免和打击的目标;而对于安全研究人员来说,root 则意味着更多 —— Towelroot、PingPongRoot、DirtyC0w、ReVent,那些有趣的漏洞和精妙的利用,承载了不少的汗水和回忆。
evilpan
2023/02/12
1K0
CVE-2018-8781:linux内核mmap整数溢出漏洞分析
sudo mknod /dev/MWR_DEVICE c 200 0 sudo chmod 777 /dev/MWR_DEVICE
De4dCr0w
2019/05/22
1.3K0
linux用户用户组与ACL
在使用Linux的过程中,经常会遇到各种用户ID(user identifier, UID)和组ID(group identifier, GID),Linux也是通过对这些ID的管理实现的自主访问控制(discretionary access control, DAC)。
落寞的鱼丶
2022/02/17
5.1K0
docker 非root用户修改mount到容器的文件出现“Operation not permitted
进入容器,在/mnt目录下进行修改文件属性的操作,出现如下错误(此时容器中的user id=0)
charlieroro
2020/03/24
5.3K0
测试用 - 4.使用eBPF逃逸容器技术分析与实践
容器安全是一个庞大且牵涉极广的话题,而容器的安全隔离往往是一套纵深防御的体系,牵扯到 AppArmor、Namespace、Capabilities、Cgroup、Seccomp 等多项内核技术和特性,但安全却是一处薄弱则全盘皆输的局面,一个新的内核特性可能就会让看似无懈可击的防线存在突破口。随着云原生技术的快速发展,越来越多的容器运行时组件在新版本中会默认配置 AppArmor 策略,原本我们在《红蓝对抗中的云原生漏洞挖掘及利用实录》介绍的多种容器逃逸手法会逐渐失效;因此我们希望能碰撞出一些攻击手法,进而突破新版本容器环境的安全能力,并使用更契合容器集群的新方式把 “任意文件写” 转化为“远程代码执行”,从而提前布防新战场。
用户3830325
2022/12/20
8020
Linux 提权总结
可见在权限位置有一个s权限。那么这个s的作用是什么呢? 答案是当其他用户执行该文件时,该文件会以root的身份执行。 这里就涉及到了Effective UID和Real UID以及Saved UID Effective UID: 程序实际操作时生效的UID Real UID: 执行该程序的用户的实际UID Saved UID: 在高权限用户降权后,保留的其原本UID (不展开说)
ConsT27
2022/02/11
7K0
Linux 提权总结
对Linux—suid提权的一些总结
suid即set user id,是一种授予文件的权限类型,它允许用户使用者以文件所有者的权限来执行文件。需要这种特殊权限的场景在Linux下很常见。 已知的可以用来提权的Linux可执行文件有: CopyNmap、Vim、find、Bash、More、Less、Nano、cp 比如常用的ping命令。ping需要发送ICMP报文,而这个操作需要发送Raw Socket。在Linux 2.2引入CAPABILITIES前,使用Raw Socket是需要root权限的(当然不是说引入CAPABILITIES就不需要权限了,而是可以通过其他方法解决,这个后说),所以你如果在一些老的系统里ls -al $(which ping),可以发现其权限是-rwsr-xr-x,其中有个s位,这就是suid:
FB客服
2021/09/16
5.1K0
K8S 生态周报| Docker和containerd 全版本漏洞公布,请及时处理
近期在 Docker 中发现了一个 影响所有版本的安全漏洞 CVE-2022-24769,该漏洞已经在 Docker 最新的版本 v20.10.14 中修复。
Jintao Zhang
2022/03/30
4150
相关推荐
Linux Capabilities 入门:让普通进程获得 root 的洪荒之力
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验