Podman 루트리스 컨테이너와 Copy Fail 악용: 폭발 반경 분석
CVE-2026-31431(일명 “Copy Fail”)의 공개는 리눅스 커뮤니티에 큰 파장을 일으켰습니다. 이 취약점은 로컬 비특권 사용자가 커널이 특정 파일 작업을 처리하는 방식을 악용해 루트 쉘을 얻을 수 있게 하며, 사실상 읽기 전용이어야 할 파일을 덮어쓸 수 있게 합니다.
컨테이너화를 사용하는 사람들에게 이 영향은 매우 중요합니다. 컨테이너는 종종 외부 서비스, CI/CD 작업, 개발 환경을 격리하는 데 사용됩니다. 공격자가 원격 코드 실행(RCE)을 통해 foothold를 얻으면, Copy Fail이 컨테이너 내부에서 권한 상승에 사용될 수 있습니다. 이 글은 Podman의 루트리스 컨테이너 환경에서 이 취약점을 구체적으로 살펴보고, 다양한 설정이 공격의 "폭발 반경"에 어떤 영향을 미치는지 탐구합니다.
루트리스 컨테이너 이해하기
Copy Fail의 영향을 이해하려면 먼저 Podman이 루트리스 컨테이너를 어떻게 구현하는지 알아야 합니다. 전통적인 Docker 데몬 모델—루트 권한 데몬이 컨테이너를 생성하는 방식—과 달리, Podman은 fork/exec 모델을 사용합니다. 컨테이너 프로세스는 podman run 프로세스의 직접적인 자식이며, 이를 시작한 사용자의 UID를 상속받습니다.
사용자 네임스페이스와 UID 매핑
Podman은 리눅스 사용자 네임스페이스를 활용해 격리를 제공합니다. 이를 통해 컨테이너 내부에서는 하나의 UID를, 호스트에서는 다른 UID를 사용할 수 있습니다. 예를 들어, 루트리스 컨테이너 내부에서 root(UID 0)로 실행되는 프로세스는 호스트에서는 비특권 사용자(예: UID 1001)로 매핑됩니다.
이 매핑은 /etc/subuid에 의해 관리되며, 비특권 사용자가 네임스페이스 프로세스에 할당할 수 있는 UID 범위를 정의합니다. 따라서 컨테이너 내부에서 root라 하더라도 호스트 관점에서는 비특권 사용자이므로, 호스트 시스템에 손상을 입히는 능력이 크게 제한됩니다.
리눅스 캡빌리티
리눅스의 루트 권한은 단일 개념이 아니라 여러 "캡빌리티"로 분할됩니다. Podman은 이러한 캡빌리티를 사용해 컨테이너 프로세스에 세밀한 권한을 부여합니다. 예를 들어 apt를 통해 패키지를 설치하려면 CAP_CHOWN과 CAP_SETUID 같은 캡빌리티가 필요합니다.
기본적으로 루트리스 루트풀 컨테이너(네임스페이스 내부에서 root로 실행)는 광범위한 캡빌리티를 부여받습니다. 이는 공격 표면을 넓히는 결과를 초래합니다. 보다 안전한 접근 방식은 "루트리스 비루트" 컨테이너를 실행하는 것으로, 컨테이너 내부와 외부 모두 비특권 사용자로 실행하고 불필요한 캡빌리티를 모두 제거합니다.
Copy Fail 악용 테스트
실제 테스트에서 Copy Fail은 다양한 Podman 설정에서 권한 상승을 시도하는 데 사용되었습니다. 이 악용은 일반적으로 비밀번호 입력 없이 su 명령을 실행할 수 있는 파이썬 스크립트를 다운로드하고 실행하는 형태이며, 이를 통해 루트 쉘을 얻습니다.
루트리스 루트풀 컨테이너
프로세스가 이미 컨테이너 내부에서 root로 실행되고 있는 설정에서는 Copy Fail이 중복됩니다. 사용자는 이미 악용이 제공하려는 권한을 가지고 있기 때문입니다. 그러나 이 프로세스는 호스트에서는 여전히 비특권 사용자에 매핑되므로, 호스트의 루트 파일에는 접근할 수 없습니다.
루트리스 비루트 컨테이너
이 경우 악용이 강력해집니다. 비특권 사용자(예: foo)로 실행되는 컨테이너에서 Copy Fail은 프로세스를 컨테이너 root로 성공적으로 상승시킵니다.
공격자는 이제 컨테이너 내부에서 root 권한을 가지고 네임스페이스 루트가 소유한 파일에 접근할 수 있지만, 여전히 호스트 사용자의 권한에 제한됩니다. 호스트 실제 루트 사용자가 소유한 파일에는 접근할 수 없습니다.
상승 방지
두 가지 주요 Podman 플래그를 사용해 Copy Fail 악용의 즉각적인 효과를 저지할 수 있습니다:
--security-opt=no-new-privileges: 프로세스가 추가 권한을 얻는 것을 방지합니다. 이 플래그가 설정되면su악용은 루트 쉘을 제공하지 못하고 사용자는 여전히foo상태에 머뭅니다.--cap-drop=all: 모든 캡빌리티를 제거하면 마찬가지로 특권 루트 쉘로의 상승을 차단합니다.
이 플래그들은 su 악용을 막지만, 근본적인 취약점—페이지 캐시에서 읽기 전용 파일을 덮어쓸 수 있는 능력—은 여전히 존재합니다. 완전한 해결을 위해서는 커널 패치를 적용해야 합니다.
방어 심층 전략: 폭발 반경 제한
단일 보안 경계만으로는 완벽히 방어할 수 없으므로, 침해된 컨테이너가 끼칠 수 있는 피해를 최소화하기 위한 방어‑심층 전략이 필요합니다.
읽기 전용 파일시스템
--read-only 플래그(또는 --read-only-tmpfs=false와 함께 사용)를 적용하면 컨테이너 루트 파일시스템이 읽기 전용으로 마운트됩니다. 이는 공격자가 악성 바이너리를 쓰거나 시스템 설정을 변경하는 것을 방지하지만, 메모리 내 파이프 명령을 통한 실행 자체를 막지는 못합니다.
리소스 제한
cgroup을 활용해 메모리, CPU, PID 수 등을 제한하면 침해된 컨테이너가 호스트나 다른 컨테이너에 대한 서비스 거부(DoS) 공격을 수행하는 것을 방지할 수 있습니다.
최소 이미지
공격자가 사용할 수 있는 도구를 최소화하는 것이 핵심 단계입니다. ubuntu와 같은 베이스 이미지는 curl, python3 등 다양한 바이너리를 제공해 악용을 쉽게 합니다. -slim 이미지, Alpine Linux, 혹은 "distroless" 이미지를 사용하면 쉘과 패키지 관리자가 제거되어 공격자가 익스플로잇을 부트스트랩하기 훨씬 어려워집니다.
네트워크 방화벽
iptables 혹은 nftables를 이용해 인/아웃바운드 연결을 제한하면 침해된 컨테이너가 명령·제어(C2) 서버와 통신하거나 내부 네트워크에서 횡방향 이동을 수행하는 것을 차단할 수 있습니다.
비판적 관점 및 반론
주된 악용 예제가 su를 통한 루트 쉘 획득에 초점을 맞추고 있지만, 커뮤니티 논의에서는 Copy Fail의 진정한 위험이 더 넓다고 강조합니다.
"이 취약점은 컨테이너와 공유되어야 할 모든 파일을 사실상 쓰기 가능하게 만들기 때문에, 폭발 반경이 많은 상황에서 매우 커집니다. 'su' 예제처럼 보편적으로 간단히 악용되지 않더라도 말이죠."
또한 일부 연구자는 Copy Fail이 컨테이너 간 횡방향 공격에 사용될 수 있다고 지적합니다. 두 컨테이너가 동일한 베이스 이미지 레이어를 공유한다면, 악의적인 컨테이너가 그 공유 레이어의 파일을 덮어써서 같은 비특권 사용자로 실행되는 다른 컨테이너를 손상시킬 수 있습니다.
리눅스 커널의 격리 메커니즘(네임스페이스, seccomp)이 고보안 환경에 충분하지 않을 수 있다는 의견도 늘어나고 있으며, 일부는 보다 강력한 보안 경계로서 MicroVM을 권장하고 있습니다.
결론
Podman의 루트리스 아키텍처는 컨테이너 root가 호스트 root가 되지 않도록 함으로써 표준 루트풀 Docker 설치보다 훨씬 높은 보안 태세를 제공합니다. 그러나 Copy Fail이 보여주듯, 컨테이너는 절대적인 방벽이 아닙니다. 루트리스 실행에 캡빌리티 삭제, no-new-privileges 옵션, 최소 이미지 사용을 결합하면 침해 시 폭발 반경을 크게 줄일 수 있습니다. 궁극적으로 가장 효과적인 방어는 시기적절한 커널 패치와, 컨테이너 경계가 결국 뚫릴 수 있다는 전제 하에 설계된 다층 보안 접근법을 병행하는 것입니다.