..

KANS) 컨테이너의 시작 chroot 감옥 체험기

컨테이너는 가상머신과 다르게 커널을 공유해서 바로 프로세스를 실행 할 수 있는 구조이다. 아래 그림과 같이 호스트 커널을 공유하지만 각각의 독립된 사용자 공간을 가진다.

1-0-0

이러한 컨테이너의 시작은 특정 디렉토리를 루트(/)로 지정하여 해당 경로에 프로세스를 가두는 방식이다.

1-0-1

루트디렉토리 아래에 있는 디렉토리 A를 루트로 지정하여 들어가게되면

1-0-2

안에 있는 프로세스들은 디렉토리 A를 루트로 갇히게 된다.

사용되는 명령어

chroot :프로세스가 실행되는 루트 디렉토리를 특정 디렉토리로 변경한다.

pivot_root :프로세스의 마운트 네임스페이스에서 루트 마운트를 특정 디렉토리에 마운트 되어있는 새 마운트로 변경한다.

1-1-0

감옥생성

# 루트가 될 디렉토리(new-root)를 생성 후 필요한 명령어 복사
mkdir -p new-root/bin
cp $(which bash) new-root/bin

# 명령어 실행 시 참조하는 라이브러리 복사
ldd /bin/bash
cp /lib/x86_64-linux-gnu/libtinfo.so.6 new-root/lib/
...

이런식으로 필요한 명령어들을 하나씩 복사해서 넣어준다. chroot 명령어를 통해 들어가보면 만들어진 루트 디렉토리에 갇힌 것을 확인할 수 있다.

1-1

탈옥

탈옥하기에 앞서 위와 같이 명령어를 하나씩 다 넣기는 힘드니 만들어진 이미지를 사용하여 새로운 루트를 만들어준다.

# nginx이미지를 tar로 압축 후 nginx-root 디렉토리에 압축 해제
docker export $(docker create nginx:latest) | tar -C nginx-root -xvf -

# 탈옥 코드
cat <<EOF >escape_chroot.c
#include <sys/stat.h>
#include <unistd.h>

int main(void)
{
  mkdir(".out",0755);
  chroot(".out");
  chdir("../../../../../");
  chroot(".");

  return execl("/bin/sh", "-i", NULL);
}
EOF

# 위의 코드를 컴파일하여 nginx-root에 escape_chroot이라는 실행 파일을 생성
gcc -o nginx-root/escape_chroot escape_chroot.c

chroot 명령어를 사용하여 들어가 탈옥코드로 만든 실행파일을 실행시키면 진짜 루트 디렉토리로 나오게된다.

1-2

문제 해결을 위해 pivot_root와 mount namespace를 이용하여 다시 가두기

# 디렉토리 생성 후 800M사이즈의 파일시스템을 마운트
mkdir new-root
mount -n -t tmpfs -o size=800M none new-root

# 위에서 테스트 했던 nginx-root의 파일들을 복사하여 새 디렉토리에 붙여넣기
cp -r nginx-root/* new-root/

# 마운트 포인트 디렉토리 생성
mkdir new-root/old-root

# 호스트에 영향을 주지않기 위해 mount namespace분리
unshare -m

escape_chroot을 돌려도 만들어진 루트 디렉토리에서 빠져나오지 못하는 것을 확인할 수 있다.

1-3

이렇게 격리된 환경의 리소스들은 해당 네임스페이스 안에서만 볼 수 있고 변경되지만, 자식은 부모의 네임스페이스를 상속받기 때문에 호스트에서 컨테이너 파일들에 접근과 프로세스 확인이 가능하다.

1-4

컨테이너 파일에 접근하여 파일 생성 및 삭제가 가능하고

1-5

호스트에서 컨테이너의 프로세스 확인 후 프로세스를 종료시키면 컨테이너 안에서도 종료되는 것을 확인할 수 있다.


참고