Linux内核漏洞分析系列——CVE-2023-0386
Linux内核漏洞分析系列——CVE-2023-0386
bwner 看雪学苑 2023-05-20 17:59
CVE ID: CVE-2023-0386 [overlay]Influence Version: (Linux 5.11 ~ 5.19 besides 5.15)Vulnerability: LPE
Patch:fs/overlayfs/copy_up.c(
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/diff/fs/overlayfs/copy_up.c?id=4f11ada10d0a
)
这个漏洞是overlay内核组件存在逻辑问题,导致具有suid权限的文件可以未授权访问,最后实现本地提权。
上篇文章我将内核版本切换到了5.16.12(看chenaotian师傅的帖子说是5.15存在问题无法复现,不巧的是我一直用的都是5.15,的确无法使用)。
分析漏洞之前先来看看组件是做什么的:Overlay是一种Linux文件系统组件,用于实现一种可写的文件系统层(upper layer),并通过挂载两个文件系统来实现文件层次结构的组合。具体来说,它是一种用于容器和虚拟化环境的轻量级文件系统,通常用于在只读根文件系统之上创建一个可写层,以便应用程序可以在其中进行更改而不会影响到根文件系统。下图是overlay的分层结构:
1.Merged层
:这是一个虚拟的、合成的视图,它将 lower 层和 upper 层合并在一起。当用户访问 Overlay 文件系统时,他们看到的是这个 merged 层。在这个层中,来自upper层的更改会覆盖lower层的相应文件。对于同名文件upper层中的文件优先级更高。对于同名目录,则合并,只判断目录中的文件是否有上下层覆盖屏蔽关系。
【用户最终看到的是这个视图】
2.Upper层:这是一个可写的文件系统层,它存储所有对 lower 层文件的更改。这包括文件修改、创建和删除操作。
3.Workdir目录:这是一个与 upper 层在同一文件系统中的可写目录,用于存储一些中间数据和元数据,以支持 OverlayFS 的正常运行。
4.Lower层:这是基础文件系统层,只读。一个Overlay文件系统可以有一个或多个lower层。此层拥有写时复制特性(copy-up),当想要修改lower层不可修改的fileC时,先拷贝副本到upper层,修改后呈现到merged视图。
OverlayFS 操作流程
挂载OverlayFS文件系统
上面的命令可以将”lowerdir”和”upper”目录堆叠到merged目录,”workdir”工作目录要求是和”upperdir”目录同一类型文件系统的空目录,”workdir”是 OverlayFS 内部使用的工作目录,不需要用户自己创建。”lower”、”upper” 和 “merged” 等路径需要替换为实际存在的路径,我们需要在根目录下创建这三个目录。
各层新建文件对比
在lower层新建文件:
当我们在lower层新建文件后,可以看到新文件也出现在merged目录,upper目录无变化。[+] lower [+] merged [ ] upper
在merged层新建文件:
当我们在merged层新建文件后,可以看到新文件也出现在upper目录,lower目录无变化。[+] merged [+] upper [ ] lower
在upper层新建文件:
当我们在upper层新建文件后,可以看到新文件也出现在merged目录,lower目录无变化。[+] merged [+] upper [ ] lower
在merged层修改文件的变化
merged层是一个统一的视图,当在merged新建文件时,可以看到新文件也出现在upper目录,lower目录无变化。也就是merged和upper是相互作用的。
在merged层中:
来自upper层的更改会覆盖lower层的相应文件。对于同名文件,upper层中的文件优先级更高。对于同名目录,则合并,只判断目录中的文件是否有上下层覆盖屏蔽关系。
注意事项
需要注意以下卸载overlayFS后各层文件的变化,这也是后续漏洞利用的重要信息。
挂载overlayFS后,在lower层新增文件,merged也会新增文件,upper无变化;从merged层修改lower层文件,upper新增修改后的文件,接着卸载overlayFS,merged文件会消失,upper保留修改后的文件。
保留下来的文件权限跟lower层是一样的:
漏洞触发原理
漏洞的逻辑
overlayFS 所有文件都可以正常拷贝,包括没有在当前用户命名空间中映射的文件(也就是nobody文件)。
拷贝文件并不只是拷贝文件的内容,包括文件的元数据,也就是文件的属主信息、时间戳、权限信息、还有扩展信息如capbilities等都会一起拷贝过来。引发的风险就是,如果下层文件系统是一个用户文件系统(如fuse),用户高度可控,可以自定义任何文件,但该文件系统存在限制(如nosuid),那么本漏洞就允许将下层用户自定义的suid文件从一个nosuid 文件系统拷贝到一个正常文件系统中,导致非法的suid文件获得suid特权。进而造成提权。
“nosuid”是Linux文件系统中的一种标志,它是在挂载文件系统时使用的选项之一。如果一个文件系统被标记为”nosuid”,则在该文件系统上的任何文件或目录上设置的”setuid”或”setgid”位都会被忽略,这意味着即使一个可执行文件被设置为具有特权用户的权限,也不会在执行该文件时获得特权,从而提高了系统的安全性。
“nosuid”是在挂载时对文件系统进行了限制,但是文件系统可以生成一个”suid”的文件,即使因为限制不起作用。
用户命名空间
什么是用户命名空间?
用户命名空间:用户命名空间(User Namespace)用于隔离用户ID(UID)和组ID(GID)。可以思考一下C++中的命名空间,假设一个程序中用了两个相同函数名的函数(不是指重载),就会出现函数名冲突的问题,用命名空间就可以调用时避免冲突。具体来说,假设在主机上存在一个名为”james”的用户,其UID为0。如果直接使用overlay进行文件系统叠加,并在容器中以”james”用户身份启动进程,该进程将会在容器中使用UID为0的用户,与主机中的”james”用户相同,容易造成权限混淆的问题。使用命名空间后,一个容器中的root用户(UID 0)可能在主系统中被映射为一个非特权用户,起到了权限隔离的作用。主要使用命令:[unshare]
动手实践:
◆创建nobody新用户命名空间并查看权限
输出如下:
此处看到新用户命名空间中的uid为nobody。
nobody是一个特殊的系统用户,通常被用作一个不需要拥有系统特权的用户来运行某些服务或进程。在Linux中,每个用户都有一个对应的UID(UserID),而nobody用户的UID通常是65534。当一个进程运行在一个不同的用户命名空间时,由于该命名空间中没有与该UID相关联的用户名,所以该UID会被识别为nobody用户。因此,当您在一个新的用户命名空间中使用unshare命令创建一个新的shell时,该shell中的UID会被识别为nobody。(nobody权限比普通用户低)
◆创建root权限的新用户命名空间,获取的root权限只限于新用户命名空间
◆(sudo) 创建root权限的新用户命名空间并查看权限
–map-root-user 能够指定创建的用户uid,需要root权限,并且创造的root用户能在用户命名空间之外也能执行特权操作。
输出如下:
再对比一下在不同用户命名空间下文件的拥有者:
fuseFS 操作流程
我在漏洞的逻辑中提到了:”nosuid”是在挂载时对文件系统进行了限制,但是文件系统可以生成一个”suid”的文件,即使因为限制不起作用。
我们在这里通过fuse文件系统创建一个文件,文件的要求如下:
1.设置文件所有者为root。
2.设置SUID权限位,SUID权限位会让执行该文件的用户拥有与文件拥有者相同的权限。
创建fuse_suid程序
我们先创建一个符合上面要求的文件,后续再对符合要求的文件进行修改。首先安装依赖:
新建文件fuse_suid.c,用来生成一个程序,运行程序后能够挂载fuseFS到一个空目录,挂载后目录下包含一个可执行文件hello,执行hello中的命令获取shell(此处使用chenaotian师傅的poc):
编译:
运行:
使用mount | grep mount_fuse
查看挂载目录的属性,可以看到fuseFS被设置成了nosuid:
查看hello文件属性:
运行hello后得到shell。可以看到即使是suid,拥有者为root,运行后也是普通用户:
上述操作流程可通过下面脚本自动部署,方便调试fuse_suid.c文件:
注意事项
自己构建poc时,报错时请注意检查回调函数。例如无法使用ls列出fuseFS挂载目录下的文件:ls: reading directory ‘mount_fuse/’: Function not implemented
需要注册一个新的FUSE回调函数hello_readdir
,该函数列出文件系统中的文件和目录。
完整利用链
通过上面对两个文件系统的学习,梳理了漏洞的逻辑后,我们可以得到以下完整利用流程:
1.在fuseFS中制作拥有suid权限拥有者为root用户的后门程序hello,挂载fuseFS到自建的mount_fuse目录。
2.创建新用户命名空间(root用户)、新挂载命名空间(用于overlayFS挂载)、新PID命名空间。
3.挂载overlayFS到新用户命名空间,创建各层文件目录,将第一步挂载的mount_fuse目录设置为lower层。
4.将hello的suid属性从fuseFS的nosuid环境中带出来,带到upper层,退出新用户命名空间后hello保留suid及拥有者为root用户。
5.将挂载的文件系统都退出,最后会在upper文件留下提权可执行后门hello进行提权。
各步骤执行代码如下:
◆在fuseFS中制作拥有suid权限拥有者为root用户的后门程序hello,挂载fuseFS到自建的mount_fuse目录:
◆创建新用户命名空间(root用户)、新挂载命名空间(用于overlayFS挂载)、新PID命名空间:
◆挂载overlayFS到新用户命名空间,创建各层文件目录,将第一步挂载的mount_fuse目录设置为lower层:
挂载后可以看到merged目录无变化,mount_fuse目录成了lower层。
◆将hello的suid属性从fuseFS的nosuid环境中带出来,带到merged层
这里涉及到一个问题:修改lower层的文件,会触发copy-up操作。使用touch命令在尝试创建一个已经存在的文件的时候不会覆盖已经存在的文件,而是只修改文件的访问时间和修改时间的时间戳,而时间戳信息也算文件的attr扩展信息,该信息被修改同样会触发overlay文件系统的copy-up操作。
根据我们在OverlayFS操作流程-注意事项中提到的,因为只修改了lower层文件的时间戳,没有覆盖文件,最后卸载所有文件系统时会把带着suid权限的hello程序保存在upper层目录。
◆最后执行被带出到upper层的hello程序,完成提权。
◆利用链流程文件变化对比:
补丁分析
补丁新增部分:
前面我们已经知道了存在漏洞的地方在于copy-up操作,新增的这部分代码的含义是:kgid_has_mapping()函数用于检查当前组标识是否在给定的用户命名空间中有映射关系。它接受两个参数:用户命名空间和组标识(GID),并返回一个布尔值。
如果 kuid_has_mapping() 函数的结果为假(零),即用户标识没有映射关系,或者kgid_has_mapping()函数的结果为假(零),即组标识没有映射关系,整个条件表达式将被认为是假(零)。只有当两个条件都为真时,整个表达式才会被认为是真(非零)。
所以更新补丁后,当进行copy-up操作时,如果目标下层文件的属主用户或属组用户在当前命名空间中没有映射的话(也就是拥有者为nobody用户)就会失败,防止了自定义的文件进行未授权访问。
****### 可利用条件
可利用条件:已开启overlay文件系统(我使用的系统默认开启了overlayFS,可以根据上一篇编译源码make menuconfig
查看是否系统为默认开启,参数分别为CONFIG_SLUB_DEBUGOVERLAY_FS、CONFIG_FUSE_FS)。
查看系统是否已开启overlay文件系统的方法
(1) ls /sys/module/overlay/
如果有输出,则表示overlay文件系统已经被内核模块加载;如果输出为空,则表示没有加载overlay模块。
(2) grep CONFIG_OVERLAY_FS /boot/config-$(uname -r)
根据输出的结果,可以看到CONFIG_OVERLAY_FS
的值为m
,表示该文件系统功能已编译到模块中,可以在需要时加载该模块来使用 Overlay 文件系统,该功能已经开启。(m或者y都代表已开启)
通过学习和复现此漏洞的细节,希望大家能够通过本文了解学习fuseFS和overlayFS的操作机制并懂得如何利用此漏洞提权。在写本文时我也发现了自身很多不足之处。如果在文中发现哪处存在问题,也欢迎大家在留言区指出。
参考链接
https://github.com/chenaotian/CVE-2023-0386
https://github.com/xkaneiki/CVE-2023-0386
https://blog.csdn.net/luckyapple1028/article/details/78075358
看雪ID:bwner
https://bbs.kanxue.com/user-home-951654.htm
*本文
为看雪论坛优秀文章,由看雪论坛 bwner 原创,转载请注明来自看雪社区
#往期推荐
1、AntCTF x D³CTF 2023 d3op复盘笔记
4、贵阳大数据及网络安全精英对抗赛Reverse EZRE_0解题
球分享
球点赞
球在看