FlawOpen

flawopen.com/Teardowns/cve-2024-32002-git-submodule-rce

● CVE-2024-32002 · CVSS 9.0 · 심각
보안 연구 · FlawOpen

CVE-2024-32002: Git 서브모듈 심볼릭 링크 원격 코드 실행 취약점 (RCE)

CVE-2024-32002에서는 조작된 Git 저장소가 재귀 clone 중 대소문자 충돌 symlink를 통해 .git에 파일을 쓸 수 있었고, 검토 전에 hook이 실행될 수 있었습니다. Git의 upstream 수정은 clone 전후에 하위 모듈 대상 경로를 확인하고 예상하지 못한 내용이 있으면 중단합니다. 수정된 Git 버전으로 업그레이드하세요.

💡 알기 쉬운 설명 (ELI5)

안내판이 A와 a를 같은 문으로 취급하는 창고를 떠올려 보세요. 하위 모듈 경로가 기록실(.git)로 연결되는 안내판을 통과하면 한 보관 구역에 둘 파일이 Git의 제어 기록에 들어갈 수 있습니다. Git 수정은 clone 전후에 대상 경로를 확인하고 예상하지 못한 내용이 있으면 중단합니다.

핵심 개념 및 용어

Git 서브모듈 (Submodule)
상위 저장소 내부의 하위 디렉터리로 임베드된 별도의 Git 저장소.
심볼릭 링크 (Symlink)
경로 조회를 다른 대상 파일이나 디렉터리로 리디렉션하는 파일 시스템 링크 객체.
대소문자 충돌 (Case Collision)
NTFS 및 APFS에서 대소문자만 다른 두 경로가 동일한 파일 노드를 가리키는 현상.
Git 훅 (Hooks)
.git/hooks/ 디렉터리에 위치하여 체크아웃 완료 시 자동 실행되는 스크립트 파일.

프레임워크 매핑

근본 원인 분석 (Root Cause)

symlink를 지원하는 대소문자 비구분 파일 시스템에서 조작된 저장소는 대소문자 접기 후 symlink와 충돌하는 하위 모듈 경로를 지정할 수 있었습니다. 재귀 clone 중 Git은 clone을 위해 만든 디렉터리를 해당 symlink로 바꾸고 하위 모듈 worktree 밖의 .git에 파일을 쓸 수 있었습니다. 그곳에 놓인 hook은 사용자가 checkout을 확인하기 전에 실행될 수 있었습니다. upstream 패치는 기존의 비어 있지 않은 하위 모듈 대상 경로로 clone하는 것을 거부하고, clone 후 .git 메타데이터 외의 내용이 있는 대상 경로도 거부합니다.

단계별 공격 실행 흐름

단계 1

재귀 clone이 저장소가 제어하는 메타데이터를 처리합니다

사용자는 symlink가 활성화된 대소문자 비구분 파일 시스템에서 신뢰할 수 없는 저장소를 재귀적으로 clone합니다. 저장소에는 대소문자 접기 후 충돌하는 하위 모듈 경로와 추적 항목이 있습니다.

단계 2

경로 충돌이 신뢰 경계를 넘습니다

Git은 충돌하는 하위 모듈 경로를 예상한 worktree가 아니라 symlink를 통해 해석할 수 있었습니다. 영향을 받은 경계는 재귀 하위 모듈 clone 중 Git의 파일 시스템 경로 처리입니다.

단계 3

파일이 Git 메타데이터에 기록됩니다

리디렉션된 쓰기는 저장소의 .git 영역에 hook을 둘 수 있습니다. Git 프로젝트 공지는 clone이 진행되는 동안 그곳에 hook이 기록되어 실행될 수 있다고 설명합니다.

단계 4

검토 전에 clone이 코드를 실행할 수 있습니다

Git은 clone 작업 중 hook을 실행하므로 사용자가 체크아웃 파일을 검토하기 전에 코드가 실행될 수 있습니다. 수정된 버전은 대상 상태를 확인하고 예상하지 못한 파일이 있으면 중단합니다.

소스 코드 비교: 취약한 구현 vs 보안 패치

이 코드는 Git upstream C 변경의 clone_submodule() 발췌이며 독립 실행 가능한 전체 함수는 아닙니다. 수정 전에는 표시된 clone 경로에 대상 콘텐츠를 확인하는 대응 검사가 없었습니다. 수정은 기존의 비어 있지 않은 checkout 디렉터리를 거부하고 clone 후 .git 메타데이터만 존재하는지 확인합니다. 보조 함수가 디렉터리 항목을 검사하며 전체 구현은 upstream commit에 있습니다.

취약한 구현
if (!file_exists(sm_gitdir)) {
    if (safe_create_leading_directories_const(sm_gitdir) < 0)
        die(_("could not create directory '%s'"), sm_gitdir);

    if (run_command(&cp))
        die(_("clone of '%s' into submodule path '%s' failed"),
            clone_data->url, clone_data_path);
}
보안 강화 패치
struct stat st;

if (!file_exists(sm_gitdir)) {
    if (clone_data->require_init && !stat(clone_data_path, &st) &&
        !is_empty_dir(clone_data_path))
        die(_("directory not empty: '%s'"), clone_data_path);

    if (safe_create_leading_directories_const(sm_gitdir) < 0)
        die(_("could not create directory '%s'"), sm_gitdir);

    if (run_command(&cp))
        die(_("clone of '%s' into submodule path '%s' failed"),
            clone_data->url, clone_data_path);

    if (clone_data->require_init && !stat(clone_data_path, &st) &&
        !dir_contains_only_dotgit(clone_data_path)) {
        char *dot_git = xstrfmt("%s/.git", clone_data_path);
        unlink(dot_git);
        free(dot_git);
        die(_("directory not empty: '%s'"), clone_data_path);
    }
}

엔지니어링 및 시스템 보안 강화 체크리스트

출처