여기서 잠깐! 페이지란?
리눅스 운영체제는 메모리를 동일한 크기로 잘게 쪼개어 관리하는데 이때 쪼개진 최소 단위를 책의 한 장과 같다고 하여 페이지(Page)라는 이름이 붙었어요. 리눅스는 물론 윈도우즈, 맥OS 등 현대의 대표적인 운영체제들에서 한 페이지는 대개 4KB (4096 바이트) 크기입니다.
해킹, 보안에 관심 있다면 최근에 한 번쯤 들어봤을 취약점이 있죠?
바로 Copy Fail(CVE-2026-31431)인데요.
지난 4월 29일, Theori의 Xint 팀이 발견한 리눅스 커널 권한 상승 취약점으로, 페이지 캐시 내용의 4 바이트를 원하는 값으로 덮어쓸 수 있는 취약점입니다. 4 바이트, 얼핏 보면 사소해 보이는 크기지만 이 덮어쓰기를 반복하면 디스크 원본 파일은 건드리지 않은 채 메모리에 올라온 setuid 프로그램을 변조할 수 있고, 이를 통해 root 권한 획득까지 이어질 수 있어 치명적이에요.
CISA는 공개 직후 Copy Fail을 KEV 카탈로그에 추가했고, 미연방 기관에는 빠른 시일 내 패치를 지시했어요. 2017년 이후 출시된 거의 모든 주요 리눅스 배포판이 영향을 받았다는 점에서 Copy Fail은 최근 가장 주목받는 커널 취약점 중 하나입니다.
이름은 들어봤지만 정확히 어떤 원리인지 아직 잘 모르겠다면, 이번 게시글이 도움 될 거예요.
Copy Fail의 핵심 개념인 페이지 캐시(Page Cache)부터 차근차근 살펴본 뒤, Copy Fail의 공격 과정에서 페이지 캐시가 어떻게 이용되는지 설명할게요. 취약점의 내부 코드를 깊게 파고들지 않고 Copy Fail의 원리를 쉽게 풀어낼 예정이에요. 끝까지 읽어보고 왜 Copy Fail이 위험하고 가장 주목받고 있는지 이해해 보세요!
리눅스 운영체제는 컴퓨터의 처리 속도를 빠르게 만들기 위해 페이지 캐싱(Page Caching)이라는 개념을 사용합니다.
SSD나 HDD와 같은 디스크는 RAM에 비해 읽고 쓰는 속도가 수십 배에서 수만 배 느려요. 이러한 속도 차이를 개선하기 위해, 자주 사용되는 파일 데이터를 페이지 단위로 RAM에 미리 복사해두는 기술을 페이지 캐싱이라고 합니다.
파일을 읽고 쓸 때 매번 느린 디스크에 접근하는 대신 RAM에 복사된 데이터를 사용함으로써 속도를 개선하는 원리인데요. 이때 RAM에 복사된 파일 데이터를 바로 페이지 캐시라고 부릅니다.
📚
여기서 잠깐! 페이지란?
리눅스 운영체제는 메모리를 동일한 크기로 잘게 쪼개어 관리하는데 이때 쪼개진 최소 단위를 책의 한 장과 같다고 하여 페이지(Page)라는 이름이 붙었어요. 리눅스는 물론 윈도우즈, 맥OS 등 현대의 대표적인 운영체제들에서 한 페이지는 대개 4KB (4096 바이트) 크기입니다.
물류 시스템에 비유하면 쉽게 이해할 수 있어요. 디스크는 외곽 지역에 있는 거대한 메인 물류창고와 같습니다. 매번 먼 메인 물류창고까지 다녀오는 번거로움을 줄이기 위해 한 번 가져온 상품은 가까운 진열대에 놓아두고 다음 요청부터는 진열대에서 바로 꺼내 쓰는 방식이에요. 이렇게 하면 사람들이 자주 찾는 상품일수록 자연스럽게 진열대에 오래 유지되죠. 이때 진열대에 놓인 상품들이 바로 페이지 캐시에 해당합니다.
더티 비트(Dirty Bit)는 페이지가 가진 속성 중 하나로, 페이지의 데이터가 수정되었는지를 나타내는 1 비트 크기의 플래그(Flag)입니다. 페이지마다 하나씩 존재해요. 더티 비트는 Copy Fail의 은닉성을 이해하는 데 도움 되는 개념이에요.
디스크에 저장된 원본 데이터와 메모리에 페이지 형태로 올라온 데이터 사이의 일관성을 유지하기 위해 사용하는데요. 비트의 상태가 나타내는 의미는 다음과 같습니다.
0 (Clean): 아래 그림과 같이 페이지의 데이터가 변경되지 않았음을 나타냅니다. 디스크의 원본과 페이지 캐시의 내용이 일치한 상태예요.
1 (Dirty): 아래 그림과 같이 페이지의 데이터가 수정되어 디스크의 원본과 내용이 달라졌음을 나타냅니다. 이 상태인 페이지를 더티 페이지(Dirty Page)라고 부릅니다.
리눅스는 프로세스가 파일을 수정할 때 곧바로 디스크에 쓰지 않아요. 대신 페이지 캐시의 내용을 먼저 수정한 뒤 더티 비트를 1로 설정합니다. 이후 커널이 주기적으로 더티 페이지들을 모아 디스크에 한꺼번에 반영하고, 반영이 끝난 페이지들의 더티 비트를 다시 0으로 되돌립니다.
리눅스 커널 안에는 인증 암호화(AEAD, Authenticated Encryption with Associated Data) 알고리즘을 처리하는 모듈이 여럿 구현되어 있어요. AEAD는 데이터를 암호화하는 동시에 무결성까지 검증하는 알고리즘으로, 디스크 암호화, IPsec, TLS 등 다양한 곳에서 쓰입니다. AEAD를 만드는 방식은 크게 두 가지인데요, AES-GCM이나 ChaCha20-Poly1305처럼 하나의 알고리즘 자체로 암호화와 인증을 같이 수행하는 방식이 있고, 일반 암호 알고리즘과 인증 알고리즘을 조합해 AEAD를 만들어 주는 헬퍼 템플릿 방식도 있습니다.
Copy Fail 취약점은 후자의 헬퍼 템플릿 중 하나인 authencesn에 존재해요. ESN이라는 옵션이 적용된 IPsec 패킷을 처리하기 위한 모듈인데, 쉽게 말해 일반 데스크톱 환경에서 직접 호출될 일은 거의 없는 비교적 특수한 목적의 코드입니다.
여기서 중요한 점이 있어요.
리눅스 커널은 AF_ALG라는 특수한 소켓 인터페이스를 통해 커널 내부의 암호화 모듈을 일반 사용자 프로세스에도 노출하고 있기 때문에 root가 아닌 일반 사용자도 socket()을 사용해서 authencesn을 비롯한 커널 암호화 헬퍼에 입력을 주입할 수 있는데요.
문제는 이 authencesn 모듈에 페이지 캐시에서 4 바이트를 원하는 값으로 덮어쓸 수 있는 커널 취약점이 존재한다는 점입니다. 4 바이트만 덮어쓸 수 있다고 하면 영향이 작아 보이지만 여러 번 반복해 넓은 영역을 변조하거나 프로그램 내부의 권한 검사 로직 일부를 변조한다면 이야기가 달라져요. 특히 이 덮어쓰기가 디스크의 원본 파일을 대상으로 발생하는 것이 아니라 페이지 캐시에서 일어난다는 점이 가장 핵심입니다.
일반적으로 쓰기 권한(w)이 없는 파일은 수정할 수 없죠. 예를 들어 su나 passwd 같이 root가 소유하고 setuid가 설정된 프로그램은, 일반 사용자는 읽기(r)와 실행(x)만 할 수 있으니 프로그램을 수정할 수 없습니다. 하지만 Copy Fail을 이용하면 페이지 캐시를 변조할 수 있고, 해당 프로그램이 실행될 때 커널은 디스크의 원본 대신 변조된 페이지 캐시를 사용하게 되는 것이죠. 결과적으로 쓰기 권한이 없는 실행 파일의 동작을 바꾸어 실행할 수 있게 됩니다.
📚
여기서 잠깐! setuid 비트란?
보통 프로그램은 실행한 사용자의 권한으로 동작하죠. 그런데 setuid 비트가 설정된 프로그램은 EUID (Effective UID)가 프로그램을 실행한 사용자가 아니라 그 파일의 소유자 권한으로 설정되어 동작해요.
예를 들어 /usr/bin/passwd는 소유자가 root이며 setuid 비트가 설정되어 있습니다. 따라서 일반 사용자가 실행하더라도 EUID가 root로 설정되어 실행되는 것이에요. 덕분에 일반 사용자는 /etc/shadow 같은 파일에 passwd 명령어로 접근해 비밀번호를 변경할 수 있습니다.
하지만 일반 사용자가 다른 사용자의 비밀번호를 마음대로 바꿀 수 있는 것은 아닙니다. EUID가 root여서 root 권한을 행사할 수 있는 것과 별개로, 프로그램은 RUID (Real UID)를 통해 실제로 프로그램을 실행한 사용자가 누구인지는 여전히 확인할 수 있기 때문인데요. 일반 사용자가 실행했다면 자기 자신의 비밀번호만 변경할 수 있고, root가 실행한 경우에만 다른 사용자의 비밀번호까지 변경할 수 있게 RUID를 검사하도록 passwd 프로그램의 로직이 짜여 있습니다.
그림과 함께 단계별로 공격 과정 예시를 확인해 보세요!
디스크상에 root가 소유하고 있으며 setuid가 설정된 바이너리 /usr/bin/su가 존재합니다.
이후 /usr/bin/su가 페이지 캐싱 되어 메모리상에 페이지 캐시로 올라옵니다.
이때 디스크의 원본 파일과 메모리의 페이지 캐시는 같은 내용을 가지고 있으니 더티 비트는 0이에요.
Copy Fail 취약점을 이용하면 메모리에 올라와 있는 /usr/bin/su의 페이지 캐시를 변조할 수 있습니다. 최초 공개된 PoC에서는 setreuid()와 execve()를 호출하는 ‘160 바이트 크기의 자그마한 ELF 실행 파일’로 페이지 캐시를 덮어썼어요. 해당 ELF는 RUID까지 root로 설정한 뒤 셸을 실행하도록 만들어졌습니다.
여기서 특히 눈여겨봐야 할 점은 페이지 캐시의 내용은 변조되었지만 더티 비트는 여전히 0으로 유지된다는 점이에요.
일반 사용자가 /usr/bin/su 바이너리를 실행하면 디스크에 있는 프로그램이 아니라 아래 그림과 같이 페이지 캐시에 있는 ‘160 바이트의 자그마한 ELF’가 실행됩니다. 그 결과 root 권한을 가진 셸이 실행되는 것이죠.
위 과정에서는 예시로 /usr/bin/su를 사용했지만 반드시 su만 가능한 것은 아닙니다. root가 소유자이며 setuid 비트가 설정된 실행 파일이라면 얼마든지 공격 대상으로 이용될 수 있어요.
페이지 캐시의 내용은 변조되었지만 더티 비트는 여전히 0으로 유지되었는데요, 이는 Copy Fail 취약점을 이용한 비정상적인 쓰기 과정이 일반적인 페이지 수정 경로를 거치지 않기 때문이에요. 즉, 페이지 캐시의 내용은 바뀌지만 커널은 더티 비트를 갱신하지 않는 것이죠.
🚩 여기서는 Copy Fail을 이용한 권한 상승 사례로 setuid가 설정된 실행 파일을 예시로 들었지만 문제는 실행 파일에만 국한되지 않아요. root 권한으로 보호되어 수정되면 안 되는 파일이라면 동일한 방식으로 영향을 받을 수 있으며, /etc/passwd와 같은 주요 시스템 파일이 변조될 경우에도 심각한 문제가 발생할 수 있습니다.
실제로는 더 복잡한 원리가 숨어 있지만 Copy Fail의 원리를 간략하게 살펴보면 위와 같답니다.
Copy Fail은 페이지 캐시의 내용은 변조하지만 더티 비트를 건드리지 않아요. 그로 인해 커널은 해당 페이지를 디스크에 반영해야 할 더티 페이지로 인식하지 못합니다.
그 결과 변조된 내용은 페이지 캐시에만 남고 디스크에 있는 원본 파일은 그대로 유지됩니다. 즉, 프로그램을 실행할 때는 변조된 페이지 캐시가 사용되지만 디스크 기반 해시 검사나 파일 무결성 검사에서는 원본 파일이 깨끗하게 보이는 것이죠.
물론 변조가 메모리에서만 발생하므로 재부팅되거나 페이지 캐시가 비워지면 변조된 내용이 사라질 수 있습니다. 하지만 이러한 특성 때문에 오히려 Copy Fail이 디스크에 흔적을 남기지 않고 권한 상승을 수행할 수 있는 은닉성을 갖게 됩니다.
페이지 캐시를 이해하고 나면 Copy Fail이 완전히 낯선 유형의 취약점이 아니라는 점도 이해할 수 있는데요. 리눅스에는 예전부터 디스크 파일을 직접 수정할 권한은 없지만 페이지 캐시를 덮어써 권한 상승을 하는 취약점들이 있었습니다. 대표적인 사례가 Dirty Pipe와 Dirty COW예요.
Copy Fail과 Dirty Pipe는 특히 닮았습니다. Copy Fail은 커널 암호화 모듈에서, Dirty Pipe는 파이프 버퍼 관련 기능에서 발생했지만 둘 다 결과적으로 파일의 페이지 캐시를 변조하죠.
Dirty COW는 Copy Fail이나 Dirty Pipe처럼 디스크 원본은 그대로 두고 페이지 캐시만 조용히 바꾸는 유형은 아닙니다. Dirty COW는 Copy-On-Write 처리 과정의 레이스 컨디션을 이용하여 읽기 전용 파일을 변조한다는 점에서 유사해요.
Copy Fail이 공개된 이후에 다른 연구자들도 각기 다른 방식으로 페이지 캐시를 덮어쓰는 취약점을 공개했습니다. 대표적인 사례로는 Dirty Frag와 DirtyDecrypt이 있어요. 아래 표에서 각 취약점의 특징을 확인해 보세요!
취약점 | 취약점이 발생한 부분 | 은닉성 | 익스플로잇 안정성 |
|---|---|---|---|
Copy Fail (CVE-2026-31431) (공개: 26.04) | 커널 암호화 모듈. AF_ALG, splice(), authencesn 처리 과정에서 페이지 캐시 페이지가 쓰기 대상으로 잘못 사용됨 | 디스크 원본은 그대로 두고 페이지 캐시만 변조할 수 있어 디스크 기반 무결성 검사를 우회하기 쉬움 | 확정적으로 페이지 캐시를 변조할 수 있음 |
Dirty Pipe (CVE-2022-0847) (공개: 22.03) | 파이프 버퍼 관련 기능. 초기화되지 않은 pipe_buffer.flags 때문에 파일 페이지 캐시에 데이터가 잘못 병합됨 | 페이지 캐시가 변조되지만 디스크 원본에는 바로 남지 않을 수 있음 | 조건이 맞으면 비교적 안정적으로 재현 가능함 |
Dirty COW (CVE-2016-5195) (공개: 16.10) | Copy-On-Write 처리 과정. COW 레이스 컨디션으로 읽기 전용 파일 매핑에 쓰기가 가능해짐 | 페이지 캐시와 관련은 있지만, 공격 방식에 따라 실제 파일 변경으로 이어질 수 있어 흔적이 남을 수 있음 | 레이스 컨디션 기반이라 타이밍, 커널 상태, 시스템 부하에 영향을 받음 |
Dirty Frag (CVE-2026-43284, CVE-2026-43500) (공개: 26.05) | 네트워크 암호화 기능(IPsec, RxRPC) 처리 과정에서 페이지 캐시 페이지가 쓰기 대상으로 잘못 사용됨 | 디스크 원본은 그대로 두고 페이지 캐시만 변조할 수 있어 디스크 기반 무결성 검사를 우회하기 쉬움 | 조건이 맞으면 안정적으로 재현 가능함 |
DirtyDecrypt (공개: 2605) | 네트워크 보안 계층(RxGK)이 받은 데이터를 복호화 할 때 실수로 페이지 캐시에 쓰기가 발생함 Fedora, Arch, openSUSE 같은 일부 배포판에서만 동작함 | 디스크 원본은 그대로 두고 페이지 캐시만 변조할 수 있어 디스크 기반 무결성 검사를 우회하기 쉬움 | 안정적이나, 취약점을 일으키는 기능이 켜진 배포판에서만 작동됨 |
취약점을 이해하는 것과 직접 손으로 확인하는 건 또 다른 경험이죠.
드림핵에서 Copy Fail을 경험할 수 있는 워게임 문제를 풀어보세요!
자세한 원리를 몰라도 괜찮아요. 인터넷에 공개된 PoC를 찾아서 문제 환경에서 실행해 보고, root 권한을 획득해 플래그를 얻는 것만으로도 이 취약점의 위험성을 더 직관적으로 느낄 수 있을 거예요. 어떤 PoC가 문제 환경에 적합한지 그리고 PoC를 어떻게 문제 환경으로 옮겨 실행할 수 있을지 고민해 보는 것도 성장할 수 있는 계기가 될 거예요.
Copy Fail의 최초 공개 글을 확인해 보세요! → Copy Fail
코드 레벨에서 이해하고 패치 분석까지 깊이 있게 살펴보고 싶다면
티오리 Xint 팀이 직접 작성한 다음 글을 확인해 보세요. → Copy Fail: 732 Bytes to Root on Every Major Linux Distribution.
Copy Fail을 응용한 컨테이너 이스케이프까지 궁금하다면 → Copy Fail: From Pod to Host.
✨
이 게시글은 해킹 학습 플랫폼 드림핵 팀이 작성했습니다.
드림핵은 사이버 보안, 해킹을 공부할 수 있는 국내 최대 학습 플랫폼으로,
현재 8만 명이 드림핵에서 함께 해킹을 공부하고 있습니다.
더 다양한 해킹의 원리가 궁금하다면?
드림핵에서 확인해 보세요!