컨테이너 런타임, 엔진, 빌더, 그리고 샌드박스
Tip
AWS 해킹 배우기 및 연습하기:
HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기:HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
컨테이너 보안에서 가장 큰 혼란 중 하나는 서로 완전히 다른 여러 구성요소가 종종 같은 단어로 뭉뚱그려진다는 점입니다. “Docker“는 이미지 포맷, CLI, 데몬, 빌드 시스템, 런타임 스택, 혹은 단순히 컨테이너라는 개념 자체를 가리킬 수 있습니다. 보안 작업에서는 이 모호성이 문제가 되는데, 서로 다른 층이 서로 다른 보호를 담당하기 때문입니다. 잘못된 bind mount 때문에 발생한 breakout은 저수준 런타임 버그 때문에 발생한 breakout과 같지 않으며, 둘 다 Kubernetes에서의 클러스터 정책 실수와는 다릅니다.
이 페이지는 생태계를 역할별로 분리하여 섹션의 나머지 부분이 보호나 약점이 실제로 어디에 있는지 정확하게 이야기할 수 있게 합니다.
OCI As The Common Language
현대 Linux 컨테이너 스택은 종종 일련의 OCI 사양을 구사하기 때문에 상호운용됩니다. OCI Image Specification은 이미지와 레이어가 어떻게 표현되는지를 설명합니다. OCI Runtime Specification은 런타임이 프로세스를 어떻게 시작해야 하는지—네임스페이스, 마운트, cgroups, 그리고 보안 설정을 포함하여—설명합니다. OCI Distribution Specification은 레지스트리가 콘텐츠를 어떻게 노출하는지를 표준화합니다.
이것이 중요한 이유는 한 도구로 빌드한 컨테이너 이미지를 다른 도구로 실행할 수 있는 이유와 여러 엔진이 동일한 저수준 런타임을 공유할 수 있는 이유를 설명해 주기 때문입니다. 또한 여러 제품에서 보안 동작이 비슷하게 보일 수 있는 이유도 설명합니다: 많은 제품이 동일한 OCI 런타임 구성을 구성하여 동일한 소수의 런타임에 넘기고 있기 때문입니다.
Low-Level OCI Runtimes
저수준 런타임은 커널 경계에 가장 가까운 구성요소입니다. 네임스페이스를 실제로 생성하고, cgroup 설정을 쓰고, capabilities와 seccomp 필터를 적용하며, 최종적으로 execve()로 컨테이너 프로세스를 실행하는 부분입니다. 사람들이 기계적 수준에서 “컨테이너 격리“에 대해 논할 때, 명시적으로 말하지 않더라도 보통 이 계층을 가리킵니다.
runc
runc는 참조 OCI 런타임이며 가장 잘 알려진 구현체로 남아 있습니다. Docker, containerd, 그리고 많은 Kubernetes 배포에서 널리 사용됩니다. 많은 공개 연구와 익스플로잇 자료가 runc 스타일 환경을 타깃으로 삼는데, 그 이유는 이들이 일반적이며 runc가 많은 사람들이 리눅스 컨테이너를 떠올릴 때의 기본 기준을 정의하기 때문입니다. 따라서 runc를 이해하는 것은 전형적인 컨테이너 격리에 대한 강한 정신적 모델을 독자에게 제공합니다.
crun
crun은 C로 작성된 또 다른 OCI 런타임으로 현대 Podman 환경에서 널리 사용됩니다. cgroup v2 지원, 강력한 rootless 사용성, 낮은 오버헤드로 자주 호평받습니다. 보안 관점에서 중요한 것은 다른 언어로 작성되었다는 점이 아니라 동일한 역할을 수행한다는 점입니다: OCI 구성을 커널 아래에서 실행 중인 프로세스 트리로 바꾸는 구성요소입니다. rootless Podman 워크플로우가 자주 더 안전하게 느껴지는 것은 crun이 모든 문제를 마법처럼 해결해서가 아니라, 그 주변의 전체 스택이 user namespaces와 최소 권한 쪽으로 더 기울기 때문인 경우가 많습니다.
runsc From gVisor
runsc는 gVisor에서 사용하는 런타임입니다. 여기서는 경계의 의미가 실질적으로 달라집니다. 일반적인 방식으로 대부분의 syscall을 호스트 커널에 직접 전달하는 대신, gVisor는 userspace 커널 계층을 삽입하여 Linux 인터페이스의 큰 부분을 에뮬레이트하거나 중재합니다. 결과는 몇 가지 추가 플래그가 있는 일반적인 runc 컨테이너가 아니라, 호스트-커널 공격 표면을 줄이기 위한 목적의 다른 샌드박스 설계입니다. 호환성과 성능 트레이드오프는 그 설계의 일부이므로 runsc를 사용하는 환경은 일반 OCI 런타임 환경과는 다르게 문서화되어야 합니다.
kata-runtime
Kata Containers는 워크로드를 경량 가상 머신 내부에서 실행함으로써 경계를 더 멀리 밀어냅니다. 관리적으로는 여전히 컨테이너 배포처럼 보일 수 있고 오케스트레이션 층도 그렇게 취급할 수 있지만, 기본적인 격리 경계는 고전적인 호스트-커널 공유 컨테이너보다는 가상화에 더 가깝습니다. 이는 컨테이너 중심 워크플로를 포기하지 않으면서 더 강한 테넌트 격리를 원할 때 Kata가 유용하게 만듭니다.
Engines And Container Managers
저수준 런타임이 커널과 직접 대화하는 구성요소라면, 엔진이나 매니저는 사용자와 운영자가 보통 상호작용하는 구성요소입니다. 이미지 풀, 메타데이터, 로그, 네트워크, 볼륨, 라이프사이클 작업, 그리고 API 노출을 처리합니다. 이 계층은 실제 타협이 여기서 많이 발생하기 때문에 매우 중요합니다: 런타임 소켓이나 데몬 API에 대한 접근은 저수준 런타임 자체가 완전히 정상이라도 호스트 침해와 동일할 수 있습니다.
Docker Engine
Docker Engine은 개발자에게 가장 인지도가 높은 컨테이너 플랫폼이며 컨테이너 용어가 Docker 모양으로 굳어진 이유 중 하나입니다. 일반적인 경로는 docker CLI에서 dockerd로, 그리고 dockerd는 containerd 및 OCI 런타임과 같은 저수준 구성요소를 조정합니다. 역사적으로 Docker 배포는 종종 rootful이었고, 따라서 Docker socket에 대한 접근은 매우 강력한 프리미티브였습니다. 그래서 실제적인 privilege-escalation 자료의 많은 부분이 docker.sock에 초점을 맞추는 이유입니다: 프로세스가 dockerd에 특권 컨테이너를 생성하거나 호스트 경로를 마운트하거나 호스트 네임스페이스에 조인하도록 요청할 수 있다면 커널 익스플로잇이 전혀 필요하지 않을 수 있습니다.
Podman
Podman은 더 데몬리스한 모델을 중심으로 설계되었습니다. 운영적으로 이것은 컨테이너가 장기 실행 권한을 가진 데몬을 통해 관리되는 것이 아니라 표준 Linux 메커니즘을 통해 관리되는 프로세스라는 생각을 강화하는 데 도움이 됩니다. Podman은 많은 사람들이 처음 배운 고전적 Docker 배포보다 훨씬 강한 rootless 스토리를 가지고 있습니다. 이것이 Podman을 자동으로 안전하게 만들지는 않지만, 기본 위험 프로필을 특히 user namespaces, SELinux, 그리고 crun과 결합할 때 상당히 바꿉니다.
containerd
containerd는 많은 현대 스택에서 핵심 런타임 관리 구성요소입니다. Docker 아래에서 사용되며 Kubernetes 런타임 백엔드 중 하나로도 지배적입니다. 강력한 API를 노출하고, 이미지와 스냅샷을 관리하며, 최종 프로세스 생성을 저수준 런타임에 위임합니다. containerd에 대한 보안 논의는 containerd 소켓이나 ctr/nerdctl 기능에 대한 접근이 Docker의 API 접근만큼 위험할 수 있다는 점을 강조해야 합니다. 인터페이스와 워크플로우가 덜 “개발자 친화적“으로 느껴지더라도 마찬가지입니다.
CRI-O
CRI-O는 Docker Engine보다 더 좁게 초점을 맞춥니다. 범용 개발자 플랫폼이 되기보다는 Kubernetes Container Runtime Interface를 깔끔하게 구현하는 데 초점을 맞춰 빌드되었습니다. 이로 인해 특히 Kubernetes 배포와 OpenShift 같은 SELinux-중심 생태계에서 흔하게 사용됩니다. 보안 관점에서 이 좁은 범위는 개념적 혼란을 줄여주기 때문에 유용합니다: CRI-O는 ‘Kubernetes를 위해 컨테이너를 실행하는’ 계층의 일부이지 모든 것을 아우르는 플랫폼이 아닙니다.
Incus, LXD, And LXC
Incus/LXD/LXC 시스템은 Docker 스타일 애플리케이션 컨테이너와 구분할 가치가 있습니다. 이들은 종종 system containers로 사용되기 때문입니다. 시스템 컨테이너는 보통 더 완전한 userspace, 장기 실행 서비스, 더 풍부한 디바이스 노출, 그리고 더 광범위한 호스트 통합을 가진 경량 머신처럼 보일 것으로 기대됩니다. 격리 메커니즘은 여전히 커널 프리미티브지만, 운영적 기대치는 다릅니다. 따라서 여기서의 잘못된 구성은 종종 “나쁜 앱-컨테이너 기본값“보다는 경량 가상화나 호스트 위임에서의 실수처럼 보입니다.
systemd-nspawn
systemd-nspawn는 systemd에 네이티브하고 테스트, 디버깅, OS 유사 환경 실행에 매우 유용하다는 점에서 흥미로운 위치를 차지합니다. 클라우드 네이티브 프로덕션 런타임의 지배자는 아니지만, 실습실과 배포 지향 환경에서 자주 등장하므로 언급할 가치가 있습니다. 보안 분석 관점에서 이것은 “컨테이너” 개념이 여러 생태계와 운영 스타일을 포괄한다는 또 다른 상기시켜 줍니다.
Apptainer / Singularity
Apptainer (예전 이름 Singularity)는 연구 및 HPC 환경에서 흔합니다. 신뢰 가정, 사용자 워크플로우, 실행 모델은 Docker/Kubernetes 중심 스택과 중요한 방식으로 다릅니다. 특히 이러한 환경은 사용자가 포장된 워크로드를 실행하도록 허용하되 광범위한 권한 있는 컨테이너 관리 권한을 부여하지 않는 것에 깊은 관심을 갖는 경우가 많습니다. 리뷰어가 모든 컨테이너 환경을 기본적으로 “서버 위의 Docker“로 가정하면 이러한 배포를 심각하게 오해하게 됩니다.
Build-Time Tooling
많은 보안 논의는 런타임만 이야기하지만, 빌드 시 도구도 이미지 내용, 빌드 시 비밀 노출, 그리고 얼마나 많은 신뢰된 컨텍스트가 최종 아티팩트에 포함되는지를 결정하기 때문에 중요합니다.
BuildKit과 docker buildx는 캐싱, secret mounting, SSH 포워딩, 다중 플랫폼 빌드와 같은 기능을 지원하는 현대적인 빌드 백엔드입니다. 이러한 기능은 유용하지만 보안 관점에서는 비밀이 이미지 레이어로 leak되거나 과도하게 넓은 빌드 컨텍스트가 포함되어서는 안 될 파일을 노출할 수 있는 지점을 만듭니다. Buildah는 특히 Podman 주변의 OCI-네이티브 생태계에서 유사한 역할을 수행하며, Kaniko는 빌드 파이프라인에 권한 있는 Docker 데몬을 부여하고 싶지 않은 CI 환경에서 자주 사용됩니다.
핵심 교훈은 이미지 생성과 이미지 실행은 다른 단계이지만, 약한 빌드 파이프라인은 컨테이너가 실행되기 훨씬 이전에 약한 런타임 자세를 만들 수 있다는 것입니다.
Orchestration Is Another Layer, Not The Runtime
Kubernetes를 런타임 자체와 동일시해서는 안 됩니다. Kubernetes는 오케스트레이터입니다. Pod을 스케줄하고 원하는 상태를 저장하며 워크로드 구성으로 보안 정책을 표현합니다. kubelet은 containerd나 CRI-O와 같은 CRI 구현과 통신하고, 이 구현은 다시 runc, crun, runsc, 또는 kata-runtime과 같은 저수준 런타임을 호출합니다.
이 분리는 많은 사람이 보호를 “Kubernetes“에 귀속시키는 경우가 잘못될 수 있음을 의미합니다. 실제로는 노드 런타임에 의해 시행되는 것이거나 Pod 스펙에서 온 동작을 “containerd 기본값” 탓으로 돌리는 경우도 있습니다. 실제 보안 자세는 조합입니다: 오케스트레이터가 무언가를 요청하면 런타임 스택이 그것을 번역하고 커널이 최종적으로 그것을 강제합니다.
Why Runtime Identification Matters During Assessment
초기에 엔진과 런타임을 식별하면 이후 관찰한 많은 것들을 해석하기가 쉬워집니다. rootless Podman 컨테이너는 user namespaces가 이야기의 일부일 가능성이 높다는 것을 시사합니다. workload에 마운트된 Docker socket은 API 기반 privilege escalation이 현실적인 경로임을 시사합니다. CRI-O/OpenShift 노드는 즉시 SELinux 레이블과 제한된 워크로드 정책을 생각하게 해야 합니다. gVisor나 Kata 환경은 전형적인 runc breakout PoC가 동일하게 동작할 것이라고 가정하는 데 대해 더 신중하게 만듭니다.
이 때문에 컨테이너 평가에서 첫 단계 중 하나는 항상 두 가지 간단한 질문에 답하는 것입니다: 어떤 구성요소가 컨테이너를 관리하는가와 어떤 런타임이 실제로 프로세스를 시작했는가. 그 답이 명확해지면 나머지 환경은 보통 이해하기 훨씬 쉬워집니다.
Runtime Vulnerabilities
모든 컨테이너 탈출이 운영자 구성 실수에서 오는 것은 아닙니다. 때로는 런타임 자체가 취약한 구성요소일 수 있습니다. 이는 워크로드가 겉보기에는 신중한 구성으로 실행되고 있어도 저수준 런타임 결함을 통해 노출될 수 있기 때문에 중요합니다.
고전적 예시는 runc의 CVE-2019-5736입니다. 악성 컨테이너가 호스트의 runc 바이너리를 덮어쓸 수 있었고, 이후 docker exec 같은 런타임 호출이 있을 때 공격자가 제어하는 코드를 트리거하도록 기다릴 수 있었습니다. 익스플로잇 경로는 단순한 bind-mount나 권한 실수와 매우 다릅니다. 이는 런타임이 exec 처리 중에 어떻게 컨테이너 프로세스 공간으로 다시 진입하는지를 악용합니다.
red-team 관점에서 최소한의 재현 워크플로우는 다음과 같습니다:
go build main.go
./main
그런 다음, 호스트에서:
docker exec -it <container-name> /bin/sh
핵심 교훈은 특정 과거 익스플로잇 구현 자체가 아니라 평가상의 함의다: 런타임 버전이 취약하다면, 눈에 띄게 취약해 보이지 않는 컨테이너 구성이라도 컨테이너 내부에서의 일반적인 코드 실행만으로 호스트를 침해하기에 충분할 수 있다.
최근의 런타임 CVE들(예: runc의 CVE-2024-21626), BuildKit mount races, 그리고 containerd parsing bugs는 같은 요점을 강화한다. 런타임 버전과 패치 수준은 단순한 유지보수적 잡다한 정보가 아니라 보안 경계의 일부다.
Tip
AWS 해킹 배우기 및 연습하기:
HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기:HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.


