Copy Fail: Page Cache Poisoning을 통한 컨테이너 격리 파괴
현대적인 컨테이너화된 환경의 보안은 네임스페이스와 cgroups가 워크로드 간에 견고한 경계를 제공한다는 가정에 의 Rely on 합니다. 하지만 최근 공개된 "Copy Fail"이라는 취약점은 리눅스 커널의 근본적인 구성 요소인 페이지 캐시(page cache)를 공격 대상으로 삼음으로써 이 가정을 무너뜨립니다. 코드 실행을 달성하기 위해 취약한 레이스 컨디션이나 Use-After-Free (UAF) 버그에 의존하는 전통적인 커널 익스플로잇과 달리, Copy Fail은 캐시된 파일 내용을 다시 쓸 수 있는 결정론적인 프리미티브를 제공하여 공격자가 포드(pod) 간에 측면 이동을 하거나 호스트로 완전히 탈출할 수 있게 합니다.
Copy Fail의 메커니즘
Copy Fail의 핵심은 IPSec ESP Extended Sequence Numbers (authencesn)를 처리하는 커널 코드의 메모리 손상 결함을 악용하는 로컬 권한 상승 취약점입니다. 이 기능은 리눅스 커널 암호화 서브시스템의 유저랜드 인터페이스인 AF_ALG 소켓을 통해 권한이 없는 사용자에게 노출됩니다.
커널이 페이지 캐시의 가변 참조를 일회용 스크래치 메모리로 취급하도록 혼동시킴으로써, 공격자는 splice(2)를 사용하여 읽기 가능한 모든 파일의 페이지 캐시를 대상으로 제어된 4바이트 쓰기를 수행할 수 있습니다. 이를 통해 공격자는 물리적 디스크에 저장된 바이트를 변경하지 않고도 파일의 캐시된 버전을 수정할 수 있습니다. 이 쓰기는 표준 계정 및 overlayfs "copy-up" 메커니즘을 우회하기 때문에, 수정 사항은 공유된 하위 레이어 파일에 직접 발생합니다.
컨테이너가 취약한 이유
컨테이너 격리는 mount, network, PID, user, 그리고 IPC 네임스페이스를 통해 구현됩니다. 결정적으로, 이러한 네임스페이스 중 어느 것도 컨테이너별 페이지 캐시를 생성하지 않습니다. 커널의 페이지 캐시는 시스템 전체에서 공유됩니다.
Kubernetes 환경에서 컨테이너 이미지는 읽기 전용 하위 레이어로 구성됩니다. 공간을 절약하기 위해 containerd나 CRI-O와 같은 컨테이너 런타임은 콘텐츠 해시를 통해 이러한 레이어를 중복 제거합니다. 만약 동일한 노드에 있는 두 개의 서로 다른 포드가 기본 이미지를(예: debian:bookworm-slim 또는 python:3.12-slim) 공유한다면, 이들은 동일한 하위 호스트 inode와 address_space를 공유합니다.
Copy Fail이 페이지 캐시의 folio를 변이시키면, 동일한 address_space를 가리키는 모든 컨테이너의 모든 파일 디스크립터는 수정된 바이트를 보게 됩니다. 이는 두 가지 주요 공격 벡터를 생성합니다:
시나리오 1: 컨테이너 간 포이즈닝(Cross-Container Poisoning)
이 시나리오에서 한 포드 내에서 코드 실행 권한을 가진 공격자(또는 단순히 포드를 생성할 권한이 있는 공격자)는 널리 공유되는 기본 레이어를 공격 대상으로 삼을 수 있습니다.
- 대상 선택: 공격자는
site-packages내의 Python 모듈이나 기본 레이어 내의glibc와 같은 공유 라이브러리를 공통 파일로 식별합니다. - 쓰기: Copy Fail을 사용하여 공격자는 페이지 캐시 내의 대상 파일을 패치하기 위해 4바이트 쓰기를 연쇄적으로 수행합니다.
- 트리거: 동일한 노드에 있는 두 번째의 관련 없는 포드가 해당 모듈을 임포트하거나 해당 라이브러리를 실행할 때, 캐시된 포이즈닝된 바이트를 로드하고 공격자의 코드를 실행합니다.
이를 통해 공격자는 동일한 노드에에 있는 공격자 제어 포드 또는 침해된 포드와 기본 이미지를 공유한다는 이유만으로 강화된 백엔드 포드를 침해할 수 있습니다. 또한, 공격자가 pods/create 권한을 가지고 있다면, 의도적으로 피해자의 노드에 포드를 스케줄링하고 동일한 기본 이미지를 풀(pull)하여 포이즈닝을 트리거할 수 있습니다.
시나리오 2: 컨테이너 탈출을 통한 호스트 루트 권한 획득
Copy Fail은 권한이 없는 컨테이너에서 호스트로의 완전한 탈출을 달성하는 데에도 사용될 수 있습니다. 이 경로는 "Dirty Pipe" 탈출 패턴을 반영합니다:
runc실행 강제: 공격자는 컨테이너 내부의/bin/sh를/proc/self/exe를 가리키는 shebang으로 덮어씁니다. 관리자가kubectl exec를 실행하면,runc가 호출되고 컨테이너의 PID 네임스페이스에 고정됩니다.- 위치 파악 및 포이즈닝: 공격자는
runc프로세스를 식별하고 해당/proc/<pid>/exe심볼릭 링크를 엽니다.runc는 호스트로부터 bind-mount된 것이므로, 이는 페이지 캐시에 있는 호스트의runc바이너리에 대한 경로를 제공합니다. - 실행: 공격자는 Copy Fail을 사용하여
runcELF 헤더를 악성 페이로드로 덮어씁니다. 다음에 호스트에서runc가 실행될 때(프로브, 포드 시작 또는 다른exec를 통해), 악성 코드가 호스트의 root 권한으로 실행됩니다.
탐지 및 완화
Copy Fail의 가장 위험한 측면 중 하나는 전통적인 보안 도구에 대한 가시성 부재입니다. 디스크 상의 바이트는 변경되지 않은 상태로 유지되므로, 이미지 레지스트리 스캐너(Trivy, Clair), 에이전트리스 디스크 스캐너, 그리고 파일 무결성 모니터(AIDE, Tripwire)는 시스템이 깨끗하다고 보고할 것입니다.
효과적인 방어책
| 방어 메커니즘 | 효과성 | 비고 |
| :--- | :--- | | Kernel Patching | 높음 | 유일한 영구적 해결책은 호스트 커널을 패치된 버전으로 업데이트하는 것입니다. |
| Seccomp Profiles | 높음 | socket(AF_ALG, ...)를 차단하면 익스플로잇 프리미티브를 제거할 수 있습니다. |
| gVisor / Kata Containers | | 높음 | 이들은 별도의 커널 또는 microVM을 제공하여 공유 페이지 캐시를 제거합니다. |
| Managed MicroVMs (Fargate) | 높음 | 포드별 커널은 컨테이너 간 포이즈닝을 방지합니다.
| Runtime EDR | 부분적 | 포스트 익스플로잇 동작이나 메모리 내 페이지 불일치를 탐지지할 수 있습니다. |
결론
Copy Fail은 리눅스 페이지 캐시의 공유 특성이 컨테이너 보안의 중대한한 아키텍처적 사각지대임을 보여줍니다. 네임스페이스는 리소스의 논리적 분리를 제공하지만, 커널의 근본적인 메모리 관리는 글로벌 리소스입니다. 강력한 멀티테넌시가 필요한 조직의 경우, 이 취약점은 컨테이너를 주요 보안 경계로 사용해서는 안 되며, VM 기반 격리(또는 gVisor와 같은 샌드박스 런타임)가 고소위험 워크로드를 보호하기 위해 필수적임을 뒷받니다. |