Escaping containers using the Dirty Pipe vulnerability | Datadog Security Labs (새 탭에서 열림)
리눅스 커널의 Dirty Pipe(CVE-2022-0847) 취약점은 권한이 없는 프로세스가 읽기 전용 파일에 데이터를 쓸 수 있게 하여, 컨테이너 환경에서 호스트의 권한을 탈취하는 '컨테이너 탈출'을 가능하게 한다. 이 글은 Kubernetes 환경에서 runC 바이너리를 덮어쓰는 방식을 통해, 공격자가 격리된 컨테이너를 벗어나 호스트 수준의 관리자 권한을 획득하는 과정을 상세히 설명한다. 이는 과거 runC 취약점 패치가 성능 최적화를 위해 커널 페이지 캐시를 공유한다는 점을 역이용한 결과로, 현대적 컨테이너 런타임 구조 내의 보안 허점을 시사한다. ### 컨테이너 런타임과 OCI 명세의 이해 * Kubernetes는 컨테이너 실행을 위해 containerd나 CRI-O 같은 고수준 런타임을 사용하며, 이들은 내부적으로 runC와 같은 저수준 OCI(Open Container Interface) 런타임을 호출한다. * runC는 리눅스의 네임스페이스와 제어 그룹(cgroups)을 설정하여 프로세스를 논리적으로 격리하며, 최종적으로 `execve` 시스템 콜을 통해 사용자가 지정한 엔트리포인트를 실행한다. * 컨테이너 프로세스가 생성되는 시점에 `/proc/self/exe` 파일 기술자(File Descriptor)를 통해 호스트의 runC 바이너리에 접근할 수 있는 경로가 일시적으로 열리게 된다. ### runC 취약점의 역사적 맥락 * 과거 CVE-2019-5736 취약점은 컨테이너 내부에서 호스트의 runC 바이너리를 직접 수정하여 루트 권한을 획득하는 방식을 사용했다. * 이를 방어하기 위해 runC 개발팀은 바이너리를 복제(clone)하여 실행하거나, 호스트의 runC 바이너리를 읽기 전용으로 마운트하여 컨테이너 내부에 제공하는 패치를 적용했다. * 하지만 Dirty Pipe 취약점은 커널 페이지 캐시를 조작하여 읽기 전용 파일조차 수정할 수 있게 하므로, 성능 향상을 위해 도입된 '읽기 전용 공유 방식'이 오히려 새로운 공격 경로가 되었다. ### Dirty Pipe를 이용한 컨테이너 탈출 메커니즘 * 공격자는 권한이 없는 컨테이너 내부에서 스크립트를 실행하여 호스트의 runC가 다시 실행되기를 기다린다(예: 관리자의 `kubectl exec` 호출). * runC가 실행되는 순간, 공격 프로세스는 `/proc/<runC-pid>/exe` 경로를 통해 Dirty Pipe 취약점을 가동한다. * 이 취약점은 커널 페이지 캐시 수준에서 메모리를 덮어쓰기 때문에, 호스트의 물리적 디스크에 저장된 runC 파일은 건드리지 않으면서도 현재 실행 중인 runC 프로세스를 악성 바이너리로 교체할 수 있다. ### 공격 증명(PoC) 및 실행 과정 * 공격 스크립트는 루프를 돌며 `ps` 명령어로 `/proc/self/exe`를 참조하는 runC 프로세스의 PID를 지속적으로 감시한다. * 대상 PID가 발견되면 Dirty Pipe 익스플로잇 코드를 실행하여, 해당 프로세스가 참조하는 바이너리 데이터를 호스트 권한으로 실행될 악성 ELF 파일로 덮어쓴다. * 조작된 runC는 호스트 시스템에서 루트 권한으로 실행되며, 공격자가 의도한 명령(예: 호스트의 `/tmp/hacked` 파일 생성 등)을 수행한 뒤 호스트 전체를 장악할 수 있게 한다. ### 보안 결론 및 대응 방안 * 본 취약점은 컨테이너 격리 기술 자체가 아닌 리눅스 커널의 메모리 관리 결함에서 비롯된 것이므로, 가장 확실한 해결책은 Dirty Pipe 보안 패치가 적용된 최신 커널 버전으로 노드를 업데이트하는 것이다. * 컨테이너 환경에서는 `/proc` 파일 시스템에 대한 비정상적인 접근을 모니터링하고, 불필요한 고권한(Privileged) 컨테이너 사용을 지양하는 보안 정책이 병행되어야 한다. * 시스템 재부팅이나 캐시 초기화 시 조작된 페이지 캐시가 사라져 공격 흔적이 휘발될 수 있으므로, 실시간 침입 탐지 시스템을 통한 조기 대응이 중요하다.