
로컬 권한 상승 익스플로잇으로, Linux 커널 overlayfs를 대상으로 하는 CVE-2023-0386입니다. 취약점 상세 분석, PoC 코드, FUSE 및 사용자 네임스페이스를 활용한 단계별 익스플로잇 가이드를 포함합니다.
gcc -Wall exp.c `pkg-config fuse --cflags --libs` -o exp
./exp /tmp

이 문서의 이론적 지식(네임스페이스, overlay 파일 시스템, fuse 파일 시스템 등)은 모두 chatGPT에서 가져왔습니다.
취약점 번호: CVE-2023-0386
취약점 제품: linux kernel - overlay 파일 시스템
영향 범위: 5.11 ~ 5.19
악용 조건: unshare를 실행할 수 있거나 overlay 파일 시스템을 생성할 수 있어야 함
악용 효과: 로컬 권한 상승
직접 커널을 컴파일:
취약점 영향 범위에 포함되고 5.15 버전을 제외한(5.15는 문제가 있는 듯함) 버전을 준비하고, overlay와 fuse 두 가지 fs를 활성화합니다:
CONFIG_SLUB_DEBUGOVERLAY_FS
CONFIG_FUSE_FS
ubuntu 21.10 커널 버전 5.13.0-16-generic에서 실제로 동작함을 확인했습니다:

취약점 분석에 앞서 chatGPT에게 리눅스 커널 전문가 역할을 맡겨 보겠습니다:
(chatGPT에게 요청: 이제 당신은 리눅스 커널 전문가입니다. 제 질문에 답변을 도와주세요.)
이 취약점에 대한 공개 정보는 많지 않으며, 가장 직접적인 것은 취약점 패치 정보입니다. 패치 링크는 다음과 같습니다:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4f11ada10d0a

ovl_copy_up_one 함수에 판단 로직이 추가된 것을 볼 수 있습니다. 먼저 chatGPT에게 이 함수가 무엇을 하는지 물어보겠습니다:

즉, 이 함수는 overlay 파일 시스템에서 하위 레이어 파일을 상위 레이어로 복사하는 동작 중에 발생합니다. 그런 다음 문맥에 맞춰 이 패치에 새로 추가된 판단을 살펴보겠습니다:
static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
int flags)
{
int err;
DEFINE_DELAYED_CALL(done);
struct path parentpath;
struct ovl_copy_up_ctx ctx = {
.parent = parent,
.dentry = dentry,
.workdir = ovl_workdir(dentry),
};
if (WARN_ON(!ctx.workdir))
return -EROFS;
ovl_path_lower(dentry, &ctx.lowerpath);
err = vfs_getattr(&ctx.lowerpath, &ctx.stat,//[1] 获取底层文件系统的stat
STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT);
if (err)
return err;
//[2]补丁新加判断文件的stat属性中的用户id和用户组id是否在当前命名空间有映射
if (!kuid_has_mapping(current_user_ns(), ctx.stat.uid) ||
!kgid_has_mapping(current_user_ns(), ctx.stat.gid))
return -EOVERFLOW;
[1] 먼저 vfs_getattr 함수를 통해 하위 파일 시스템의 대상 파일 속성을 가져옵니다. vfs_getattr 함수는 파일의 struct path 구조체를 전달받아 해당 파일에 대응하는 struct stat 구조체를 얻습니다.
[1.1] ctx.lowerpath는 overlay 파일 시스템에 있는 하위 파일 시스템의 특정 파일 경로입니다. overlay 파일 시스템은 뒤에서 설명합니다.
[1.2] struct stat 구조체는 파일의 소유자, 그룹 등을 포함한 파일의 메타데이터 정보를 저장합니다. 그리고 가져온 파일 소유자 정보는 아래의 패치에 새로 추가된 판단에서 검사됩니다.
[2] 그런 다음 방금 가져온 파일의 소유자 정보와 그룹 정보를 kuid_has_mapping 함수로 검사합니다. 대상 파일의 소유자와 그룹이 현재 사용자 네임스페이스에 매핑되어 있는지 판단합니다.
[2.1] kuid_has_mapping 함수는 두 개의 매개변수를 받습니다. 하나는 struct user_namespace 사용자 네임스페이스 구조체이고, 다른 하나는 struct kuid 커널 사용자 구조체입니다. 이 함수는 주어진 사용자 정보가 주어진 사용자 네임스페이스에 매핑되어 있는지 확인합니다. 네임스페이스에서 사용자 매핑에 대한 자세한 내용은 아래에서 설명합니다.
따라서 취약점이 있는 이 함수(ovl_copy_up_one)가 실행될 때, 대상 하위 파일의 소유자 사용자나 그룹 사용자가 현재 네임스페이스에 매핑되어 있지 않으면 실패한다는 것을 알 수 있습니다.
이로써 패치의 원리는 명확해졌습니다. 그렇지만 여전히 이 취약점을 재현하려면 다음 문제들을 해결해야 합니다:
ovl_copy_up_one이 포함된 로직, 즉 overlay 파일 시스템에서 하위 레이어 파일을 상위 레이어로 복사하는 동작을 어떻게 트리거할 것인가?lowerpath는 위 로직 체인에서 정확히 어떤 역할을 하는가?이 두 가지 의문을 풀기 전에 몇 가지 기본 지식을 먼저 이해해야 합니다:
(chatGPT에게 요청: 리눅스 커널의 네임스페이스에 대해 소개해 주세요.)
Linux에서 네임스페이스(namespaces)는 리소스 격리를 구현하기 위한 커널 기능입니다. 네임스페이스를 사용하면 프로세스 그룹이 독립된 시스템 환경에서 실행되는 것처럼 보이게 하여 시스템의 보안과 관리 용이성을 높일 수 있습니다. 네임스페이스는 컨테이너 기술(예: Docker)에서 핵심적인 역할을 하며, 컨테이너가 다른 컨테이너나 호스트 시스템에 영향을 주지 않고 격리된 환경에서 실행될 수 있게 합니다.
Linux 커널은 7가지 네임스페이스(mount, pid, net, ipc, user, time, cgroup)를 지원하며, 각 네임스페이스는 특정 시스템 리소스 유형을 격리합니다. 네임스페이스는 clone, unshare, setns와 같은 일련의 시스템 호출을 통해 생성, 수정, 관리됩니다. 컨테이너 런타임(예: Docker) 및 기타 가상화 도구는 이러한 네임스페이스 기능을 활용하여 컨테이너에 독립적이고 격리된 실행 환경을 제공합니다.
취약점 패치에 새로 추가된 검사 함수 kuid_has_mapping이 관련된 것은 위 7가지 네임스페이스 중 **사용자 네임스페이스(user namespace)**입니다.
(chatGPT에게 요청: 그중에서 사용자 네임스페이스에 대해 소개해 주세요.)
사용자 네임스페이스(User Namespace)는 사용자 ID(UID)와 그룹 ID(GID)를 격리하는 데 사용됩니다. 사용자 네임스페이스를 사용하면 각 네임스페이스에서 독립적인 사용자 및 그룹 ID 집합을 사용할 수 있습니다. 즉, 한 사용자 네임스페이스의 사용자와 그룹은 다른 네임스페이스에서 다른 ID나 권한을 가질 수 있습니다. 사용자 네임스페이스는 특히 컨테이너 환경에서 시스템 보안과 관리 용이성을 향상시킬 수 있습니다.
사용자 네임스페이스의 핵심 특징은 ID 매핑입니다. 사용자 네임스페이스는 한 네임스페이스의 UID와 GID를 다른 네임스페이스의 UID와 GID로 매핑할 수 있게 합니다. 즉, 서로 다른 사용자 네임스페이스에서 동일한 UID와 GID가 다른 사용자와 그룹을 나타낼 수 있습니다. 예를 들어, 컨테이너의 root 사용자(UID 0)는 호스트 시스템에서 권한이 없는 사용자로 매핑될 수 있습니다.
우리는 다음 몇 가지만 기억하면 됩니다:
예를 들어, breeze 사용자로 새 사용자 네임스페이스를 만든 다음 해당 사용자 네임스페이스에서 root 소유의 파일을 살펴보면 그룹 소유자가 nobody로 표시됩니다:

이는 새 네임스페이스에서 root 사용자가 해당 네임스페이스를 생성한 breeze 사용자이기 때문입니다. 초기 네임스페이스의 root는 새 네임스페이스에 수동으로 매핑되지 않았으므로 새 네임스페이스에서 nobody로 인식됩니다.
따라서 여기서 이 패치의 의미를 알 수 있습니다. 복사 대상인 overlay 하위 파일 시스템의 파일은 해당 소유자(그룹) 사용자(그룹)가 현재 네임스페이스에 매핑되어 있어야 이후 복사 동작이 계속되며, 그렇지 않으면 오류를 반환합니다. 즉, nobody로 인식되는 경우 복사가 실패하게 됩니다.
(chatGPT에게 요청: Linux의 overlay 파일 시스템에 대해 소개해 주세요.)
Overlay 파일 시스템(OverlayFS라고도 함)은 Linux 커널의 가상 파일 시스템입니다. 두 개 이상의 기존 디렉터리 계층 구조("lower" 및 "upper" 레이어)를 하나의 통합된 뷰로 병합할 수 있습니다. Overlay 파일 시스템은 쓰기 작업을 위에 겹쳐진 쓰기 가능한 레이어로 리디렉션할 수 있으므로 읽기 전용 파일 시스템(예: 이미지)에 쓰기 기능을 구현할 때 매우 유용합니다. 이 방식은 경량이면서 고성능인 파일 시스템 가상화를 제공하므로 컨테이너 기술(예: Docker)에서 널리 사용됩니다.
특정 overlay 파일 시스템 디렉터리의 실제 상하위 레이어 파일이 merge 레이어 파일에 대응되는 효과는 아래 그림으로 이해할 수 있습니다:

상위 레이어 파일 시스템은 쓰기 가능하므로 사용자가 상위 레이어의 파일을 수정할 때는 직접 수정합니다. 그러나 위 그림의 file D처럼 하위 레이어 파일 시스템의 파일을 수정하려면 하위 레이어 파일 시스템이 읽기 전용이므로 file D를 상위 레이어로 복사(copy up)해 file D'로 만든 다음 수정 작업을 수행합니다. 실제로 수정되는 것은 상위 레이어로 복사된 file D'이며, 하위 레이어 파일 시스템의 file D 자체는 변경되지 않습니다. 이것이 overlay 파일 시스템의 COW(copy on write, 쓰기 시 복사)입니다:

(chatGPT에게 요청: 간단한 overlay 파일 시스템을 생성하는 실제 작업 예시를 하나 보여 주세요.)
다음 방법으로 overlay 파일 시스템을 생성하는 방법을 간단히 시연해 보겠습니다:
먼저 lower1, lower2, upper, work 디렉터리를 생성해야 합니다. 이 디렉터리들은 Overlay 파일 시스템에 사용됩니다. 동시에 병합된 뷰에 접근하기 위한 마운트 지점(예: merged)도 생성해야 합니다. 그리고 lower1과 lower2 디렉터리에 몇 가지 내용을 추가합니다:
mkdir lower1 lower2 upper work merged
echo "This is a file in lower1." > lower1/file1.txt
echo "This is a file in lower2." > lower2/file2.txt
mount 명령과 -t overlay 옵션을 사용하여 Overlay 파일 시스템을 마운트합니다. lowerdir, upperdir, workdir 매개변수를 지정해야 하며, 다음과 같습니다:
mount -t overlay overlay -o lowerdir=lower1:lower2,upperdir=upper,workdir=work merged
merge 디렉터리에서 상하위 레이어 파일 시스템의 파일을 볼 수 있습니다: