FlawOpen

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

● CVE-2024-32002 · CVSS 9.0 · Критический
Исследования · FlawOpen

CVE-2024-32002: Удаленное выполнение кода через симлинки Git Submodule (RCE)

CVE-2024-32002 позволяла подготовленному Git-репозиторию во время рекурсивного клонирования записывать в .git через символическую ссылку, конфликтующую по регистру, и запускать hook до проверки файлов. В upstream-исправлении Git проверяет каталог субмодуля до и после клонирования и прерывает операцию при неожидимом содержимом; обновитесь до исправленной версии Git.

💡 Простыми словами (ELI5)

Представьте склад, где таблички считают A и a одной дверью. Если путь субмодуля проходит через табличку, ведущую в архив (.git), файлы для одного сектора могут попасть во внутренние данные Git. Исправление Git проверяет целевой каталог до и после клонирования и останавливается при неожиданном содержимом.

Ключевые понятия и термины

Подмодуль Git (Submodule)
Отдельный репозиторий Git, встроенный как подкаталог в родительский проект.
Символическая ссылка (Symlink)
Объект файловой системы, перенаправляющий путь на другой целевой файл или директорию.
Коллизия регистра (Case Collision)
Поведение NTFS и APFS, при котором пути с разным регистром символов ведут к одному объекту.
Git-хуки (Hooks)
Исполняемые скрипты в .git/hooks/, автоматически запускаемые при выполнении post-checkout.

Связи со стандартами

  • Ссылка CWE для слабого места, описанного в этом руководстве: CWE-22 CWE-59 CWE-434

Анализ первопричины

В файловой системе без учета регистра, поддерживающей символические ссылки, подготовленный репозиторий мог задать путь субмодуля, который после свертки регистра конфликтовал с symlink. При рекурсивном клонировании Git мог заменить созданный для клона каталог этой ссылкой и записать файлы за пределами worktree субмодуля, в .git. Размещенный там hook мог выполниться до того, как пользователь проверит checkout. Upstream-патч запрещает клонирование в уже существующий непустой каталог субмодуля и после клонирования отклоняет каталог, если он содержит что-либо помимо метаданных .git.

Пошаговый сценарий атаки

Шаг 1

Рекурсивное клонирование достигает метаданных под контролем репозитория

Пользователь рекурсивно клонирует недоверенный репозиторий в файловой системе без учета регистра, где Git поддерживает symlink. Репозиторий задает пути субмодулей и отслеживаемые записи, конфликтующие после свертки регистра.

Шаг 2

Конфликт путей пересекает границу доверия

Git может разрешить конфликтующий путь субмодуля через symlink, а не внутри ожидаемого worktree. Уязвимая граница — обработка путей файловой системы при рекурсивном клонировании субмодулей.

Шаг 3

Файлы попадают в метаданные Git

Перенаправленная запись может поместить hook в каталог .git репозитория. В уведомлении проекта Git подтверждено, что hook можно записать туда и выполнить, пока клонирование еще продолжается.

Шаг 4

Клон может выполнить код до проверки

Git запускает hook в ходе клонирования, до того как пользователь сможет проверить файлы. Исправленные версии Git проверяют состояние целевого каталога и прерывают операцию при обнаружении неожиданных файлов.

Исходный код: Уязвимый vs Защищённый вариант

Это фрагмент upstream-изменения на C в clone_submodule(), а не самостоятельная функция. До исправления в показанном пути клонирования не было соответствующей проверки содержимого целевого каталога. Исправление отклоняет уже существующий непустой каталог checkout и после клонирования проверяет, что присутствуют только метаданные .git; вспомогательная функция проверяет записи каталога. Полная реализация приведена в upstream-коммите.

УЯЗВИМАЯ РЕАЛИЗАЦИЯ
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);
    }
}

Чек-лист по защите системы для инженеров

Источники