DOCKER SECURITY CTF · PART 2
hostshare-abuse: 제한된 bind mount는 어디까지 영향을 줄까
파일 업로드 취약점으로 컨테이너 내부 명령 실행 권한을 얻은 뒤, host의 특정 디렉터리 하나만 읽고 쓸 수 있는 환경을 확인한다.
실습 범위
본인이 만든 CTF를 폐기 가능한 Kali VM과 격리망에서만 검증했다. 실제 서비스나 타인의 시스템에서는 재현하지 않는다. 토큰과 완성된 해설 URL은 캡처에서 제거했다.
- bind mount는 host 경로를 컨테이너 안에 직접 연결하는 정상 기능이다.
- capability를 모두 제거해도 이미 공유된 경로의 읽기·쓰기 권한은 사라지지 않는다.
- 이번 실습의 영향은 의도적으로 공유한 디렉터리 하나로 제한된다.
- 위험도는 mount의 존재보다 대상 경로, 읽기·쓰기 모드, uid·gid가 결정한다.
1. 이번 시나리오의 역할
첫 번째 시나리오인 hostshare-abuse는 강력한 컨테이너 탈출 기법을 보여주는 문제가 아니다. 이후에 등장할 위험한 설정과 비교하기 위한 제한된 대조군이다.
공통 파일 업로드 취약점 때문에 컨테이너 안에서 명령을 실행할 수 있지만, Docker가 host의 비민감 실습 디렉터리 하나만 연결해 두었다. 목표는 다음 세 가지를 구분하는 것이다.
컨테이너에서 외부 mount를 식별할 수 있는가?
공유 경로의 파일 접근 권한이 실제로 있는가?
영향이 공유 디렉터리 밖으로 확장되는가?
2. 실행 조건과 제한
서비스는 host의 실습용 NIC에만 연결하고, host 포트 8081을 컨테이너의 웹 서버 포트로 전달한다.
hostshare-abuse:
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
volumes:
- ${HOME}/container-escape-proof:/host-share:rw
ports:
- <LAB_BIND_IP>:8081:8080
컨테이너는 이미지 기본값인 비특권 UID로 실행된다. 모든 capability를 제거하고 권한 상승도 막았다. host PID namespace, Docker socket, host 루트 파일시스템은 공유하지 않는다.

8081 문제 화면: 비특권 사용자와 제한된 rw bind mount 조건
중요한 구분
cap_drop: ALL은 프로세스의 특별 권한을 제거한다. 하지만 Docker가 이미 연결한/host-share의 일반적인 파일 접근 권한까지 취소하지는 않는다.
3. bind mount는 무엇인가
bind mount는 새로운 파일시스템이나 복사본을 만드는 기능이 아니다. host의 실제 경로를 컨테이너 mount namespace 안의 다른 위치에 연결한다.
host
└─ $HOME/container-escape-proof
│
│ bind mount (rw)
▼
container
└─ /host-share
두 경로는 이름만 다를 뿐 같은 파일을 가리킨다. 따라서 컨테이너에서 /host-share 아래 파일을 만들거나 변경하면 host 디렉터리에도 그대로 반영된다.
이 기능은 개발 소스 연결, 설정 주입, 로그 보존 등에 널리 쓰인다. 문제는 기능 자체가 아니라 무엇을 어떤 권한으로 연결했는가다.
4. 확인 과정
4-1. 공통 진입점 확보
검증 없는 파일 업로드 기능을 통해 컨테이너 내부 명령 실행 권한을 확보했다. 이 단계는 bind mount 문제가 아니라 모든 시나리오가 공유하는 애플리케이션 취약점이다.
4-2. 외부 mount 식별
경로를 미리 모르는 상황이라면 커널이 제공하는 mount metadata를 확인할 수 있다.
cat /proc/self/mountinfo
여기서는 /host-share가 컨테이너 이미지에 원래 들어 있던 경로가 아니라 외부에서 연결된 mount인지, 그리고 옵션이 rw인지 확인한다. 환경에 따라 출력 형식이 달라질 수 있으므로 특정 필드 하나만으로 단정하지 않는다.
4-3. 읽기와 쓰기 확인
공유 디렉터리에 준비된 안내 파일을 읽어 host에서 만든 파일이 보이는지 확인한다. 이어서 실습 전용 파일을 생성하거나 복사한다.
ls -la /host-share
cat /host-share/README.txt
touch /host-share/container-write-proof.txt
cp /etc/hostname /host-share/copied-from-container.txt
웹 콘솔이 셸을 거치지 않는 방식이라면 >나 파이프 같은 연산자가 기대대로 동작하지 않을 수 있다. 이 경우 원인을 특수문자 필터링으로 단정하기보다, touch나 cp처럼 셸 연산자가 필요 없는 명령으로 side effect를 확인하는 편이 정확하다.

공유 경로에서 README의 해설 경로를 확인한 화면(토큰 제거)
4-4. host에서 최종 확인
마지막으로 Kali host에서 같은 디렉터리를 확인한다.
ls -la "$HOME/container-escape-proof"
컨테이너에서 만든 파일이 host에도 나타나면 bind mount의 읽기·쓰기가 검증된 것이다. 반대로 mount되지 않은 컨테이너 내부 경로의 파일은 이 host 디렉터리에 나타나지 않아야 한다.
5. 왜 이것은 host escape가 아닌가
컨테이너에서 만든 파일이 host에 나타났다는 결과만 보면 탈출처럼 보일 수 있다. 하지만 이번 프로세스는 격리 경계를 우회해 새로운 host 자원을 얻은 것이 아니다. Docker가 설정으로 허용한 공유 경로 안에서 원래 부여된 권한을 사용했다.
- 임의로 만든 비민감 디렉터리 하나만 연결돼 있다.
- 컨테이너 프로세스는 비특권 사용자로 실행된다.
- capability와 권한 상승 경로가 제거돼 있다.
- host 루트, Docker socket, host PID namespace가 보이지 않는다.
따라서 이번 결과는 host 전체 장악이 아니라 허용된 공유 디렉터리에 대한 접근이다. 다만 같은 방식으로 ~/.ssh, /etc, 애플리케이션의 설정·비밀 디렉터리를 연결했다면 위험도는 완전히 달라진다.

제한된 bind mount가 host 전체 탈출과 다른 이유를 설명하는 해설 화면
6. 다른 시나리오와 비교
| 시나리오 | 노출 대상 | 영향 범위 |
|---|---|---|
hostshare-abuse |
비민감 실습 디렉터리 | 공유 디렉터리 내부로 제한 |
rootmount-abuse |
host 루트 전체 | uid와 파일 권한 범위에서 host 전체 |
socket-abuse |
Docker daemon socket | host Docker 관리 권한 |
세 시나리오 모두 mount 기능을 사용하지만 위험도는 전혀 다르다. 핵심은 “mount가 있다”가 아니라 “그 mount가 어떤 자원을 연결하는가”다.
7. 실무 점검과 방어
7-1. 자주 놓치는 ${HOME}
Compose의 ${HOME}은 컨테이너 사용자가 아니라 docker compose 명령을 실행한 셸 환경을 기준으로 치환된다. root 셸에서 파일을 준비하고 일반 사용자 셸에서 Compose를 실행하면 서로 다른 디렉터리를 볼 수 있다.
echo "$HOME"
ls -la "$HOME/container-escape-proof"
파일이 보이지 않을 때는 권한 문제라고 단정하기 전에 실제 source 경로가 어디로 치환됐는지 먼저 확인해야 한다.
7-2. 방어 체크리스트
- 필요한 최소 디렉터리만 연결한다.
- 쓰기가 필요하지 않다면 반드시 읽기 전용(
:ro)으로 설정한다. - host 직접 bind mount 대신 named volume을 사용할 수 있는지 검토한다.
- 비밀키, 인증정보, Docker socket, host 루트를 mount하지 않는다.
- 컨테이너를 비특권 사용자로 실행하고 불필요한 capability를 제거한다.
- 배포 전 Compose와 Kubernetes manifest의 hostPath·bind mount를 검사한다.
- 파일 업로드는 실행 불가능한 별도 저장소에 보관하고 확장자·MIME·매직바이트를 검증한다.
8. 정리
이번 시나리오는 “컨테이너에서 host 파일을 건드렸다”는 한 장면만 보고 탈출 성공이라고 판단하면 안 된다는 것을 보여준다. 먼저 Docker 설정으로 허용된 범위인지, 그 범위를 넘어 새 자원에 접근했는지를 구분해야 한다.
bind mount의 위험도는 기능의 존재가 아니라 연결한 경로의 민감도, 접근 모드와 실행 사용자의 권한으로 결정된다.
다음에는 capability를 모두 제거한 컨테이너에서도 Docker socket 하나가 노출되면 왜 host Docker daemon의 관리 경로가 열리는지 확인한다.
이 글은 본인 소유의 격리된 실습 환경에서 확인한 학습 기록이다. 실제 토큰과 완성된 해설 URL은 의도적으로 제거했다.
'정보보호' 카테고리의 다른 글
| [Docker 보안 CTF #1] file-upload-ctf-docker 전체 설계도 (1) | 2026.08.03 |
|---|---|
| 정보보호 제품, IDS/IPS 그리고 Snort (0) | 2026.04.08 |