컨테이너 탈출(Container Escape)이 에이전트 워크로드(Agent Workload)가 되어가고 있다
요약
LLM 에이전트가 기존의 보안 취약점들을 빠르게 조합하여 컨테이너 탈출을 수행하는 새로운 위협 패턴을 분석합니다. 단순한 버그가 아닌, 설정 오류와 권한 관리의 결합이 에이전트에 의해 자동화된 공격 경로로 악용될 수 있음을 경고합니다.
핵심 포인트
- LLM 에이전트가 기존 보안 취약점(Docker socket, RBAC 등)을 조합해 공격을 자동화함
- 컨테이너 탈출은 단일 버그가 아닌 설정 오류들의 연쇄적인 결과임
- 에이전트 기반 공격은 기존의 보안 허점을 더 빠르고 효율적으로 탐색함
- 오케스트레이션 환경에서 권한 간의 관계와 연결성을 고려한 보안 설계가 필수적임
에이전트 주도형 컨테이너 탈출(Container Escape)에서 무서운 점은 컨테이너 탈출 그 자체가 아닙니다.
그 말이 틀린 것처럼 들릴 수 있으니, 정확하게 말씀드리겠습니다.
Sysdig의 최신 위협 연구에 등장한 기본 요소(Primitives)들은 새로운 마법이 아닙니다. 마운트된 Docker socket은 수년 동안 좋지 않은 아이디어였습니다. 과도한 권한이 부여된 Kubernetes service account(서비스 계정) 역시 수년 동안 좋지 않은 아이디어였습니다. Privileged container(특권 컨테이너)는 위험합니다. Host namespace(호스트 네임스페이스) 트릭도 위험합니다. 애플리케이션 Pod(포드)에서 접근 가능한 Secrets(비밀 정보)도 위험합니다.
실제 운영 중인 Kubernetes 설정을 진지하게 검토해 본 사람이라면 이 중 그 어떤 것도 놀라운 일이 아닐 것입니다.
새로운 부분은 바로 운영자(Operator)입니다.
Sysdig는 LLM-harness-driven(LLM 하네스 주도형) 공격자가 취약한 marimo notebook을 악용하여, 컨테이너 및 호스트 환경을 열거(Enumerating)하고, Docker socket을 탈출 경로로 사용하며, Privileged container를 생성하고, 호스트 자격 증명을 읽은 뒤, Kubernetes service-account token(서비스 계정 토큰)을 재사용하여 Secrets를 덤프(Dump)하는 과정을 관찰했습니다.
이 부분이 바로 우리가 진지하게 고민해 봐야 할 지점입니다.
에이전트가 새로운 종류의 익스플로잇(Exploit, 공격 코드)을 발명했기 때문이 아닙니다.
기존의 실수들을 더 빠르게 조합(Compose)해 버리기 때문입니다.
공격 표면(Attack Surface)은 이미 존재했다
대부분의 보안 사고는 영화 속 줄거리처럼 일어나지 않습니다. 그것들은 누군가가 연결할 수 있을 만큼 충분히 오랫동안 열려 있는 지루한 틈새들입니다.
이 사례에서 그 틈새들은 익숙합니다.
인터넷에서 접근 가능한 애플리케이션에 취약점이 있었습니다. 워크로드(Workload)는 Docker socket에 접근할 수 있었습니다. 컨테이너 환경은 가능한 탈출 경로를 열거할 수 있을 만큼 충분한 정보를 노출했습니다. Kubernetes service-account token이 사용 가능했습니다. 해당 토큰은 Secrets를 읽을 수 있을 만큼 충분한 RBAC(역할 기반 액세스 제어) 권한을 가지고 있었습니다. Secrets에는 유용한 다운스트림(Downstream) 자격 증명이 포함되어 있었습니다.
이것은 단 하나의 버그가 아닙니다.
이것은 일련의 가정(Assumptions)들이 사슬처럼 엮인 결과입니다.
애플리케이션 팀은 notebook의 취약점에 대해 생각했을지도 모릅니다. 플랫폼 팀은 특정 워크플로우의 편의를 위해 Docker socket을 고려했을지도 모릅니다. Kubernetes 팀은 서비스 계정의 범위가 "오직" 네임스페이스(Namespace)로만 제한되어 있다고 생각했을지도 모릅니다. 보안 팀은 백로그(Backlog) 어딘가에 런타임(Runtime) 경고를 남겨두었을지도 모릅니다.
각각의 결정은 국소적으로는 용인 가능한 것처럼 보일 수 있습니다.
하지만 이들이 모이면 하나의 활주로(Runway)가 됩니다.
이것이 제가 컨테이너 보안을 고립된 강화(Hardening) 팁들의 체크리스트로 취급하는 것을 싫어하는 이유입니다. "Docker 소켓을 마운트하지 마십시오"라는 말은 옳지만, 그것이 교훈의 전부는 아닙니다. 진짜 교훈은 오케스트레이션 평면(Orchestration-plane)의 권한은 관계(Relationships)라는 점입니다. 작은 애플리케이션 침해(Compromise)라도 호스트 런타임(Host runtime)과 통신하거나, 클러스터 자격 증명(Cluster credentials)을 읽거나, 마찰 없이 비밀(Secrets)을 찾아낼 수 있게 되면 훨씬 더 심각해집니다.
에이전트(Agents)는 이러한 관계를 탐색하는 데 매우 능숙합니다.
기계의 속도가 위험을 변화시킨다
인간 공격자 또한 이 모든 것을 수행할 수 있습니다.
이 점은 중요합니다. 우리는 LLM이 갑자기 Docker 소켓 노출을 위험하게 만들었다고 가정해서는 안 됩니다. 그것은 이미 위험했습니다.
하지만 속도와 지속성(Persistence)이 위험의 운영적 형태를 변화시킵니다.
인간 공격자는 다음에 무엇을 시도할지 결정하고, 명령어를 입력하고, 출력을 검사하고, 조정하며, 시간을 낭비하지 않기 위해 머릿속에 충분한 상태(State)를 유지해야 합니다. 스크립트 기반의 공격자는 알려진 경로를 자동화할 수 있지만, 환경이 예상된 형태와 다를 경우 취약해지는 경향이 있습니다.
에이전트는 그 불편한 중간 지점에 위치합니다.
에이전트는 광범위한 열거(Enumeration)를 수행할 수 있습니다. 출력을 파싱(Parse)할 수 있습니다. 전달 메커니즘을 사용하기 전에 미리 테스트할 수 있습니다. 섹션 마커(Section markers)를 사용하여 다음 단계에서 명령어 출력을 깔끔하게 잘라낼 수 있습니다. 하나의 탈출 경로를 시도하고, 결과를 관찰한 뒤, 다른 경로를 선택할 수 있습니다. 인간이 모든 분기점을 일일이 돌봐줄 필요 없이 "내가 컨테이너 안에 있는가?"에서 "Docker 소켓이 마운트되어 있는가?"를 거쳐 "특권 컨테이너(Privileged container)를 생성할 수 있는가?" 그리고 "Kubernetes 토큰이 있는가?"로 넘어갈 수 있습니다.
이것이 에이전트를 천재적으로 만드는 것은 아닙니다.
그것은 에이전트를 지치지 않게(Tireless) 만듭니다.
그리고 수많은 클라우드 네이티브(Cloud-native) 보안 실패 사례에서, 지치지 않는다는 것만으로도 충분합니다.
과거의 방어적 안도감은 복잡한 환경이 공격자의 속도를 늦출 것이라는 믿음이었습니다. 호스트는 이상하고, 이미지는 최소화(Minimal)되어 있으며, 서비스 계정(Service account)의 이름은 잘못 지정되어 있고, 런타임은 블로그 포스트와 다르며, 네트워크 경로는 어색합니다. 세 개의 불완전한 단서와 하나의 오도하는 에러가 존재합니다.
에이전트(Agents)는 그러한 우발적인 마찰(friction)의 가치를 감소시킵니다.
그들이 성공을 보장받지는 못하지만, 더 많은 질문을 던질 여유는 있습니다.
docker.sock은 편의를 위한 마운트가 아닙니다
Docker 소켓(Docker socket)은 유용하기 때문에 계속 살아남고 있는 인프라 지름길 중 하나입니다.
컨테이너가 이미지를 빌드하기를 원할 수도 있습니다. CI 작업이 형제 컨테이너(sibling containers)를 시작하기를 원할 수도 있습니다. 로컬 개발 도구가 서비스를 관리하기를 원할 수도 있습니다. /var/run/docker.sock을 마운트하면 모든 것이 작동합니다.
이것이 작동하는 이유는 이제 컨테이너가 호스트 데몬(host daemon)에 무언가를 요청할 수 있기 때문입니다.
그것이 바로 위험한 이유이기도 합니다.
만약 워크로드(workload)가 호스트 Docker 데몬과 통신할 수 있다면, 특권 컨테이너(privileged container)를 생성하거나, 호스트 파일 시스템을 마운트하거나, 호스트 네임스페이스(host namespaces)를 공유하고, 결코 봐서는 안 될 것들을 읽을 수 있게 될지도 모릅니다. 애플리케이션은 호스트에서의 루트(root) 권한이 필요했던 것이 아닙니다. 호스트에서 루트 권한을 요청할 수 있는 무언가에 대한 접근 권한이 필요했던 것입니다.
이러한 차이는 에이전트 보안(agent security)에서 매우 중요합니다.
우리는 침해된 프로세스(compromised process)가 무엇을 할 수 있는지 묻는 데 많은 시간을 소비합니다. 하지만 그 프로세스가 어떤 제어 평면(control planes)에 도달할 수 있는지 묻는 데에도 최소한 그만큼의 시간을 할애해야 합니다.
컨테이너 런타임(container runtime)에 도달할 수 있는가?
Kubernetes API에 도달할 수 있는가?
클라우드 메타데이터(cloud metadata)에 도달할 수 있는가?
CI 자격 증명(CI credentials)에 도달할 수 있는가?
배포 도구(deployment tool)에 도달할 수 있는가?
이 중 어느 하나라도 도달할 수 있는 토큰(token)을 읽을 수 있는가?
인간 공격자에게 도달 가능한 모든 제어 평면은 기회입니다. 에이전트형 공격자(agentic attacker)에게 그것은 또한 메뉴(menu)이기도 합니다.
서비스 계정(service accounts)은 프로덕션 자격 증명입니다
Kubernetes 부분은 호스트 탈출(host escape)만큼이나 중요합니다.
서비스 계정 토큰(service-account tokens)을 지루한 클러스터 배관(cluster plumbing) 정도로 취급하기 쉽습니다. 많은 워크로드에서 이들은 자동으로 마운트됩니다. 예측 가능한 경로에 위치합니다. 환경 변수에 붙여넣은 AWS 액세스 키(AWS access key)만큼 감정적으로 눈에 띄지도 않습니다.
하지만 침해된 포드(pod)가 서비스 계정 토큰을 읽을 수 있고, 그 토큰이 Secret을 나열(list)하거나 가져올(get) 수 있다면, 애플리케이션 침해는 더 이상 단순한 애플리케이션 침해에 그치지 않습니다.
그것은 자격 증명 노출(credential disclosure) 사건입니다.
네임스페이스 전체(namespace-wide)일 수도 있고, 클러스터 전체(cluster-wide)일 수도 있습니다. 혹은 데이터베이스 비밀번호, API 키, 웹훅(webhooks), SSH 키, 또는 클라우드 자격 증명(cloud credentials)을 탈취하기에 충분한 수준일 수도 있습니다. 정확한 폭발 반경(blast radius)은 RBAC(역할 기반 액세스 제어)와 각 팀이 Secrets에 무엇을 넣어두었는지에 따라 달라집니다.
이 지점이 바로 지루한 Kubernetes 기본 설정들이 보안 아키텍처(security architecture)가 되는 지점입니다.
해당 워크로드(workload)에 서비스 계정(service account)이 정말로 필요합니까?
토큰(token)을 마운트(mount)해야 합니까?
Secrets를 읽을 수 있어야 합니까, 아니면 실제로 필요한 단 한 가지 요소만 읽을 수 있어야 합니까?
Secrets가 팀에서 어디에 두어야 할지 모르는 모든 자격 증명을 모아두는 잡동사니 서랍(junk drawer)으로 사용되고 있지는 않습니까?
토큰이 수명이 짧고 결합(bound)되어 있습니까, 아니면 사실상 모든 포드(pod) 내부에 방치된 영구적인 키와 같습니까?
이러한 질문들은 화려하지 않습니다. 하지만 이 질문들은 "공격자가 하나의 워크로드에서 코드 실행(code execution) 권한을 얻었다"와 "공격자가 환경의 절반에 해당하는 키를 수집했다" 사이의 차이를 만듭니다.
탐지는 런타임 동작(runtime behavior)에 더 가까워져야 합니다
정적 태세(Static posture)도 여전히 중요합니다.
어떤 워크로드가 Docker 소켓을 마운트하는지 알아야 합니다. 어떤 포드가 특권(privileged) 모드로 실행되는지 알아야 합니다. 어떤 서비스 계정이 Secrets를 읽을 수 있는지 알아야 합니다. 어떤 컨테이너가 광범위한 권한(capabilities)을 가졌는지, 취약한 seccomp 프로필을 사용하는지, 또는 쓰기 가능한 호스트 경로(writable host paths)를 가지고 있는지 알아야 합니다.
하지만 태세는 시작일 뿐입니다.
Sysdig 보고서가 흥미로운 이유는 올바른 곳을 보고 있다면 그 동작이 가시적이기 때문입니다. 런타임 열거(Runtime enumeration). Unix 소켓을 통한 Docker API 호출. 특권 컨테이너(privileged container) 생성. 호스트 파일 시스템 바인드 마운트(bind mounts). 네임스페이스 진입(Namespace entry). 서비스 계정 토큰 읽기. 평소라면 호출해서는 안 될 워크로드에서의 Kubernetes API 호출. 갑작스러운 Secret 목록 조회.
이것은 일반적인 "AI 공격" 신호가 아닙니다.
이것은 클라우드 네이티브(cloud-native) 런타임 동작입니다.
방어적인 해답은 제목에 "에이전틱(agentic)"이라는 단어가 붙은 제품을 구매하고 그것을 전략이라고 부르는 것이 아닙니다. 정답은 지루한 신호들이 실제로 수집되고, 보존되며, 소유권(ownership)과 연결되도록 보장하는 것입니다.
워크로드가 특권(privileged)을 가진 형제 컨테이너(sibling container)를 생성한다면, 누군가는 그 사실을 알아야 합니다.
애플리케이션 포드(pod)가 서비스 계정(service-account) 토큰을 읽고 즉시 비밀(Secrets) 목록을 나열한다면, 누군가는 그 사실을 알아야 합니다.
네임스페이스(namespace)가 일반적인 애플리케이션 동작이 아닌 탐색(discovery)처럼 보이는 API 호출을 갑자기 방출한다면, 누군가는 그 사실을 알아야 합니다.
첫 번째 경고(alert)가 반드시 "LLM 하네스(harness)가 감지되었습니다"라고 말할 필요는 없습니다.
대신 "이 워크로드는 운영자(operator)가 이를 컨트롤 플레인 피벗(control-plane pivot)으로 사용하는 것처럼 동작하고 있습니다"라고 말할 수 있습니다.
그것만으로도 이미 충분히 유용합니다.
내가 가장 먼저 확인할 것
만약 내가 이번 주에 쿠버네티스(Kubernetes) 플랫폼을 책임지는 사람이라면, 새로운 AI 위협 모델(threat model) 문서를 만드는 것부터 시작하지 않을 것입니다.
나는 인벤토리(inventory) 파악부터 시작할 것입니다.
/var/run/docker.sock을 마운트하는 모든 워크로드를 찾아내십시오. 그런 다음, 각 워크로드가 실제로는 호스트 루트(host root) 권한을 갖는 것과 다름없으므로, 마치 그것이 호스트 루트인 것처럼 그 정당성을 입증하십시오.
모든 특권(privileged) 컨테이너와 모든 hostPath 마운트를 찾아내십시오. 정당한 인프라 구성 요소인 소수와, 임시 방편(workaround)이 영구적인 해결책이 되어 존재하는 것들을 분리하십시오.
비밀(Secrets)을 읽을 수 있는 서비스 계정(service accounts)을 나열하십시오. 그런 다음, 해당 ID를 사용하는 애플리케이션이 런타임(runtime)에 실제로 그 권한을 필요로 하는지 질문하십시오.
필요하지 않은 곳에서는 서비스 계정 토큰의 자동 마운트를 비활성화하십시오. 이를 모든 팀이 기억해야 하는 예외 사항이 아니라, 애플리케이션 네임스페이스의 기본값(default)으로 설정하십시오.
비밀(Secrets)을 단순한 설정 블롭(configuration blobs)이 아니라 폭발 반경(blast-radius) 객체로 간주하십시오. 만약 한 워크로드의 토큰이 비밀(Secret)을 읽을 수 있다면, 해당 워크로드가 침해될 경우 비밀이 노출될 수 있다고 가정하십시오.
Docker 소켓 사용, 특권(privileged) 컨테이너 생성, 네임스페이스 진입, 호스트 파일 시스템 마운트, 그리고 애플리케이션 포드로부터 발생하는 비정상적인 쿠버네티스(Kubernetes) API 호출에 대한 런타임 탐지(runtime detections)를 추가하십시오.
이 중 새로운 것은 하나도 없습니다.
그것이 핵심입니다.
에이전트 주도(agent-driven) 방식이 기존의 업무를 없애는 것이 아닙니다. 오히려 오랫동안 방치되었던 기존 업무를 더욱 시급하게 만듭니다.
결론
컨테이너 탈출(Container escape)은 에이전트 워크로드(agent workload)가 되어가고 있습니다.
에이전트가 컨테이너를 발견했기 때문이 아닙니다.
에이전트들이 우리가 여기저기 흘리고 다니는 작은 액세스 조각들, 즉 런타임 소켓(runtime sockets), 마운트된 토큰(mounted tokens), 허용 범위가 넓은 RBAC(Role-Based Access Control), 호스트 경로(host paths), 취약한 프로필(weak profiles), 접근 가능한 메타데이터(reachable metadata), 그리고 너무 많은 가치가 담긴 비밀값(secrets)들을 체이닝(chaining)하는 데 능숙하기 때문입니다.
이 교훈은 "AI 공격자가 마법과 같다"는 것이 아닙니다.
이 교훈은 그보다 더 나쁘고 실질적입니다. 자율적인 하네스(autonomous harness)는 어제의 플랫폼 지름길을 오늘의 빠른 권한 상승(escalation) 경로로 바꿀 수 있다는 것입니다.
따라서 방어 기준도 그에 맞춰 움직여야 합니다.
Docker 소켓 액세스를 호스트 루트(host root)처럼 취급하십시오. 서비스 계정 토큰(service-account tokens)을 운영 환경 자격 증명(production credentials)처럼 취급하십시오. Kubernetes Secret 권한을 폭발 반경(blast-radius) 경계처럼 취급하십시오. 런타임 동작을 노이즈가 아닌 증거로 취급하십시오. 그리고 이상하고 지저분한 환경이 공격자를 안심할 수 있을 정도로 느리게 만들 것이라고 가정하는 것을 멈추십시오.
지루한 통제 수단들이 이미 옳았습니다.
에이전트들은 단지 그 통제 수단들을 더 이상 미룰 수 없게 만들었을 뿐입니다.
references
제 프로젝트를 테스트하기 위해 저는 Railway를 사용합니다. 시작할 때 20달러(USD)를 받고 싶다면 이 링크를 사용하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기