docker cp는 왜 단순 파일 복사가 아닐까: 컨테이너 경계와 호스트 권한 점검하기
컨테이너 안팎으로 파일을 복사하는 작은 운영 명령 하나가 어떻게 호스트 권한 경계를 넘을 수 있을까?
docker cp는 왜 단순 파일 복사가 아닐까: 컨테이너 경계와 호스트 권한 점검하기
- 카테고리: infra_dev
- 예상 읽기 시간: 10분
- 오늘의 질문: 컨테이너 안팎으로 파일을 복사하는 작은 운영 명령 하나가 어떻게 호스트 권한 경계를 넘을 수 있을까?
- 핵심 출처:
- Docker Engine version 29 release notes - 29.5.1 보안 수정 2026-05-18, 29.5.3 2026-06-03, 확인일 2026-06-13
PUT /containers/{id}/archiveexecutes container binary on the host - 게시일 2026-05-18, 확인일 2026-06-13- Race condition in docker cp allows creation of arbitrary empty files on the host via symlink swap - 게시일 2026-05-18, 확인일 2026-06-13
- Race condition in docker cp allows bind mount redirection to host path - 게시일 2026-05-18, 확인일 2026-06-13
- v29.5.1 · moby/moby discussion - 게시일 2026-05-18, 확인일 2026-06-13
1. 왜 지금 봐야 하나
운영 중인 컨테이너에서 로그, 덤프, 설정 파일을 꺼내거나 임시 파일을 넣을 때 docker cp는 너무 자연스럽게 쓰인다. 그래서 위험도 낮아 보인다. 네트워크를 열지도 않고, 권한을 새로 주지도 않고, 그냥 파일 하나를 복사하는 명령처럼 느껴지기 때문이다.
하지만 Docker Engine 29.5.1 릴리스는 이 감각을 다시 보게 만든다. 2026년 5월 18일 공개된 29.5.1은 docker cp와 관련된 세 가지 취약점, CVE-2026-41567, CVE-2026-41568, CVE-2026-42306을 수정했다. 6월 3일의 29.5.3까지 이어진 최근 29.x 릴리스 흐름을 보면, 컨테이너 엔진은 여전히 “개발 도구”가 아니라 호스트 root 권한을 다루는 운영 경계다.
이 글은 CVE 목록을 외우자는 글이 아니다. 핵심은 더 작고 실용적이다.
컨테이너 경계는 이미지를 실행할 때만 생기는 것이 아니라, 운영자가 나중에 붙이는 복사·디버깅·마운트 명령에서도 계속 시험된다.
최근 infra_dev 글은 GKE capacity buffer, OpenTelemetry Collector, Cloud Run worker pool, OIDC 기반 secret 축소를 다뤘다. 오늘은 스케일링이나 관측 파이프라인이 아니라 컨테이너 운영 명령이 만드는 보안 경계를 본다.
2. 핵심 개념
docker cp는 겉으로는 파일 복사지만 내부적으로는 Docker daemon이 컨테이너 파일시스템을 해석하고 archive API를 처리하는 작업이다. 이때 중요한 점은 Docker CLI가 아니라 daemon이 실제 권한을 가진다는 것이다. 많은 환경에서 daemon은 호스트 root 권한 또는 그에 준하는 권한으로 동작한다.
이번 취약점 세 묶음은 서로 다른 실패 모드를 보여준다.
-
잘못된 실행 파일 탐색 경로
GHSA-x86f-5xw2-fm2r에 따르면 압축 archive를 컨테이너로 업로드할 때 daemon이xz,unpigz같은 외부 압축 해제 바이너리를 호스트가 아니라 컨테이너 파일시스템의PATH에서 찾는 문제가 있었다. 악성 이미지가 가짜 압축 해제 바이너리를 심어두면, 그 바이너리가 daemon 권한, 즉 호스트 root 권한으로 실행될 수 있었다. -
검사와 사용 사이의 시간 차이, TOCTOU
GHSA-vp62-88p7-qqf5는docker cp의 mountpoint 생성 과정에서 경로를 확인한 뒤 실제 생성하기 전, 컨테이너 내부 프로세스가 symlink를 바꿔치기할 수 있는 race condition을 설명한다. 결과적으로 호스트의 임의 절대 경로에 빈 파일이나 디렉터리를 root로 만들 수 있었다. -
bind mount 대상의 리다이렉션
GHSA-rg2x-37c3-w2rh는 mountpoint 생성 이후 실제mount()호출 전 symlink가 바뀌면 bind mount가 의도한 컨테이너 경로가 아니라 호스트 경로로 향할 수 있다고 설명한다. writable volume이면 호스트 파일이 덮일 수 있고, read-only라도 일시적으로 경로가 가려져 장애가 날 수 있다.
이 셋의 공통점은 “컨테이너가 격리되어 있으니 파일 복사는 안전하다”가 아니라는 점이다. 파일 복사는 경로 해석, symlink 처리, mount namespace, daemon 권한이 한 번에 만나는 작업이다.
3. 최신 이슈와 연결
Docker Engine 29 릴리스 노트는 29.5.1에서 세 취약점을 함께 고쳤다고 정리한다. 특히 CVE-2026-41567은 압축 archive를 docker cp -로 pipe하거나 PUT /containers/{id}/archive API에 compressed content를 보낼 때 문제가 된다. GitHub advisory는 일반적인 docker cp ./file.txt container:/file.txt는 CLI가 기본적으로 uncompressed tar를 보내므로 해당 조건에는 걸리지 않는다고 구분한다.
이 구분이 중요하다. 모든 docker cp 사용을 당장 금지하라는 뜻이 아니라, 운영자가 “디버깅 편의”로 쓰는 변형된 사용법이 위험해질 수 있다는 뜻이다.
예를 들어 아래와 같은 패턴은 편하지만 경계를 흐린다.
cat archive.tar.xz | docker cp - mycontainer:/tmp/input
또한 두 TOCTOU 취약점은 volume mount가 있는 컨테이너와 관련된다. advisory는 volume이 없는 컨테이너는 해당 race가 발생하지 않는다고 설명한다. 반대로 말하면, 운영에서 자주 쓰는 “호스트 경로 bind mount + 실행 중 컨테이너 + 긴급 파일 복사” 조합은 꼭 점검해야 하는 조합이다.
29.5.1은 패치 버전이다. 29.5.3은 이후 안정화 릴리스로 6월 3일 공개되었다. 현재 해야 할 일은 “새 기능을 써보자”가 아니라, 빌드 머신, 개발자 노트북, 셀프호스팅 runner, 배포 서버에 남아 있는 Docker Engine 버전을 확인하고 패치 경로를 정하는 것이다.
4. 개발자 관점 해석
이 이슈를 개발자 관점에서 보면 세 가지 습관이 바뀐다.
첫째, 이미지 신뢰와 운영 명령 신뢰를 분리해야 한다.
우리는 보통 “이 이미지를 실행해도 되는가?”만 묻는다. 하지만 이번 이슈는 “이 이미지에 대해 어떤 daemon API와 운영 명령을 허용할 것인가?”도 물어야 한다. 악성 이미지가 단순히 컨테이너 내부에서만 나쁜 일을 하는 게 아니라, 운영자가 나중에 실행한 docker cp를 트리거로 경계를 넘을 수 있기 때문이다.
둘째, CI runner와 개발 장비도 운영 자산이다.
Kubernetes 클러스터 노드만 패치하고 GitHub Actions self-hosted runner, 사내 빌드 서버, QA용 Docker host를 방치하면 공급망 경로가 남는다. 특히 runner는 외부 pull request, 테스트 이미지, 빌드 산출물을 자주 다룬다. “프로덕션이 아니니까 괜찮다”가 아니라 “신뢰하지 않는 입력을 가장 많이 만나는 곳”일 수 있다.
셋째, 편의 명령은 정책으로 감싸야 한다.
docker cp, bind mount, Docker socket 접근은 모두 장애 대응 속도를 높인다. 동시에 그만큼 권한이 크다. 작은 팀에서는 이를 완전히 금지하기 어렵다. 대신 어떤 환경에서 허용하는지, 압축 archive pipe를 금지할지, untrusted container에는 복사를 하지 않을지, 누가 Docker API에 접근할 수 있는지 정해야 한다.
신뢰성·비용·보안 tradeoff도 있다. 즉시 모든 host를 재시작하며 업그레이드하면 보안은 좋아지지만 빌드 지연이나 배포 중단이 생길 수 있다. 반대로 “다음 정기 점검 때”로 미루면 공격 표면이 남는다. 현실적인 선택은 인터넷에 노출된 runner, 외부 이미지를 실행하는 host, 권한이 큰 배포 host부터 먼저 패치하고, 나머지는 짧은 일정으로 묶어 처리하는 것이다.
5. 내 프로젝트에 적용할 체크포인트
- Docker Engine 버전이 29.5.1 이상인지 확인하라. 29.5.1 미만이면 이번
docker cp취약점의 영향 범위에 들어간다. - Docker Desktop만 보는 데서 멈추지 말고, self-hosted runner, 빌드 서버, 배포 서버, 개인 개발 장비의 engine 버전을 따로 확인하라.
- 신뢰하지 않는 이미지나 외부 PR에서 만들어진 이미지를 실행하는 host가 있는지 적어라.
cat archive.tar.xz | docker cp - ...또는 archive API를 직접 호출하는 스크립트가 있는지 검색하라.- 실행 중 컨테이너에 host bind mount를 많이 붙이는 개발·운영 패턴을 점검하라.
- 긴급 대응 문서에 “untrusted running container에는
docker cp로 파일을 넣지 않는다”는 원칙을 추가하라. - Docker socket을 컨테이너에 mount하는 패턴이 있다면, 그것이 정말 필요한지 다시 확인하라. 이 글의 주제는
docker cp지만, 근본적으로 daemon API 접근은 호스트 권한과 가깝다. - 패치 전 임시 완화가 필요하면 advisory의 workaround처럼 trusted image만 실행하고, archive API 접근을 제한하고, untrusted container에 compressed archive pipe를 피하라.
6. 오늘 10분 액션
오늘은 아래 10분만 해보자.
- 로컬 또는 runner에서 버전을 확인한다.
docker version
- Docker Engine
Server버전이 29.5.1 이상인지 적는다. - 프로젝트 저장소에서 위험 패턴을 찾는다.
grep -R "docker cp -" .github scripts Dockerfile* . 2>/dev/null
- self-hosted runner를 쓴다면 runner 이미지 또는 VM provisioning 문서에 Docker Engine 버전 고정이 있는지 확인한다.
- 운영 메모에 한 줄을 추가한다.
untrusted image로 만든 running container에는 compressed archive를
docker cp -로 넣지 않는다. Docker Engine은 29.5.1 이상을 기준선으로 둔다.
이 액션의 목표는 완벽한 보안 감사가 아니다. “파일 복사도 daemon 권한 작업이다”라는 감각을 팀의 체크리스트에 넣는 것이다.
7. 더 볼 자료
- Docker Engine version 29 release notes
PUT /containers/{id}/archiveexecutes container binary on the host- Race condition in docker cp allows creation of arbitrary empty files on the host via symlink swap
- Race condition in docker cp allows bind mount redirection to host path
- v29.5.1 · moby/moby discussion
중복 회피 메모
최근 infra_dev 글은 Kubernetes/GKE 스케일링, OpenTelemetry 관측 파이프라인, Cloud Run worker backpressure, OIDC 기반 배포 secret 축소를 다뤘다. 이번 글은 그 흐름을 반복하지 않고 Docker Engine 29.5.1의 docker cp 보안 수정과 Moby advisories를 바탕으로, 컨테이너 파일 복사·archive API·bind mount·daemon 권한이 만나는 운영 보안 경계를 설명한다.
핵심 출처
로그인하면 이 글을 북마크하고, 나만 보는 한 줄 메모를 남길 수 있어요.
댓글 0
최신순 ▾혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)