Неочевидные возможности ZIP на macOS
В сентябре, во время соревнований VolgaCTF, проходила конференция, на которой я впервые рассказал о своем исследовании стандартной программы по работе с архивами в macOS. Сегодня я представляю текстовую версию своего доклада.
Предисловие
Не так давно я изучал интересный проект — защищенное хранилище файлов. Одной из задач было найти возможности обхода проверки расширения файла внутри zip-архива. Пользователь мог отправить на сервер zip-архив, но сохранялся он только в том случае, если в нем лежали файлы с определенным расширением. Данную систему хранения можно графически представить примерно так:
Схема работы защищенной системы хранения
В итоге я нашел способ обхода, однако был один нюанс: найденная проблема была не в самой системе хранения и не в ее функции проверки расширений. Всё дело было в самом zip. Казалось бы, столько статей написано про zip-архивы, что сложно удивить разбирающегося человека чем-то новым. Структура архива давно известна, написаны сотни документаций, выпущено несколько версий формата zip. Но все становится интереснее, если работа идет за ноутбуком производства Apple. В этой статье я хочу обсудить одну неочевидную особенность стандартной программы по созданию zip-архивов в macOS.
Как устроен zip-архив
Что же такое zip-архив? Zip-архив — это, в первую очередь, файл с определенной структурой. На изображении приведен пример строения типичного архива.
Структура типичного zip-архива
В типичном архиве есть три заголовка: local file header, central directory file header и end of central directory. В local file хранится информация о размере, дате изменения, а также имени файла. Кроме того, здесь находится непосредственно сам файл. В central dir содержится информация о различных атрибутах файла, о размере и имени файла, но в него не входит сам сжатый файл. В третьем заголовке можно найти информацию о количестве файлов внутри архива и их размере.
Я надеюсь, многие так и представляли себе zip-архив. Как можно заметить, имя файла дублируется в local file и в central dir. Это важно, но для начала вспомним, какие же существуют самые популярные уязвимости zip-архивов.
Известные уязвимости ZIP
Zip-slip
Zip-slip — самая известная уязвимость при работе с архивами. Очень популярна в заданиях на CTF. Позволяет распаковывать файл в произвольную директорию, с помощью »../
» можно выйти из текущей директории в родительскую. Сгенерировать такой архив можно как руками, так и с помощью утилиты Evilarc.
Пример структуры архива, созданного для эксплуатации Zip-Slip.
Zip-symlink
Эта уязвимость известна в более узких кругах, но не менее популярна на CTF. Суть заключается в том, что в архив можно вложить символьную ссылку на определенный файл. При распаковке архива ссылка будет указывать на файл, прописанный в ней. Таким образом можно получить доступ к любому файлу на уязвимом сервере. Создать символьную ссылку можно командой ln –s /etc/passwd link
. Заархивировать ссылку, а не файл, на который она указывает, можно при помощи опции -symlink
в команде zip –symlink link.zip link
.
Пример структуры архива, содержащего символьную ссылку.
Zip-bomb
Этот тип атак основан на возможностях алгоритмов сжатия внутри архива. При распаковке файлы или занимают очень много места на диске, или бесконечно рекурсивно распаковываются, в то время как сам архив занимает считанные байты.
Вернемся к защищенной системе хранения
Что же делать, если ни к чему из вышеперечисленного сервис не уязвим? Как хакать? Для этого еще раз внимательно посмотрим, как устроен zip-архив. Как я заметил выше, имя файла внутри архива дублируется в local file и в central dir. Зачем это сделано, ведь можно просто сохранять имя файла один раз? Для проверки целостности? Возможно.
Я решил, что до меня никто не пробовал проделать настолько простой трюк и написал в эти поля разные имена. Результат был неожиданным. Я получил ситуацию, при которой пользователь отправляет архивом файл с одним именем, а при разархивации на macOS получает файл с совершенно другим. В то же время система проверки расширений пропускает такой архив как безопасный.
Эээксперементы
Для тестирования этой особенности я создал четыре zip-архива: два архива с версиями zip 1(наиболее часто встречаемая версия zip) и два с версиями zip64 (по умолчанию используется при архивации на macOS). В одном архиве каждой версии я поменял имя файла в local file, а в другом — в central dir. В качестве злого расширения выбрано exe
— для наглядности. На первом изображении показаны первые два архива версии zip 1, в которых изменены имена файлов соответственно в central dir и в local file.
Первые два архива версии zip 1
На втором изображении показан архив zip64 с замененным именем файла в central dir.
Архив zip64 с замененным именем файла в central dir.
На третьем изображении показан архив zip64 с замененным именем файла в local file.
Архив zip64 с замененным именем файла в local file.
Так-так, а zip64 — это кто?
Действительно, не многие помнят, что существует множество разных реализаций формата zip. Второй по популярности — zip64 — создан как модернизированная версия обычного zip. Он позволяет хранить значительно больше данных, практически исключая ситуацию, когда пользователь не может заархивировать свою петабайтную коллекцию котиков из-за огромного количества объектов. Основные отличия zip от zip64 представлены в таблице:
Основные отличия zip от zip64.
Тесты на распаковку
Интересно, какое название будет у файла test после распаковки каждого архива на разных платформах? Я протестировал архивы в macOS 11, Windows 10 и Debian 9. Для распаковки я использовал стандартные методы, однако в Debian для удобства я использовал консольную утилиту unzip. Так, на первом изображении показан результат распаковки в Windows. Как можно заметить, Windows при разархивации смотрит на имя файла, которое указано в структуре central dir.
Распаковка на Windows.
На втором изображении показан результат распаковки в Debian. Как видим, Debian разархивировал тестовые архивы точно так же, как и Windows, отдав предпочтение имени, указанному в central dir.
Распаковка на Debian.
На третьем изображении показан результат распаковки в macOS. Ого, что-то новое. Расширения магическим образом поменялись! Получается, что стандартная утилита для работы с архивами в macOS при распаковке подобных архивов смотрит на имя файла, указанное в local file.
Распаковка на macOS.
Интересно, что во всех остальных популярных архиваторах, а также во всех популярных библиотеках к основным языкам программирования названия файлов архива берутся именно из central dir.
Бага не в стандарте, а в macOS
С полученными знаниями систему хранения на схеме можно представить иначе.
Было:
Стало:
В результате, код модуля системы, которая отвечает за проверку расширений файлов внутри архива, пропустит специальным образом модифицированный архив, а пользователь macOS, который этот архив получит и разархивирует, может столкнуться с появлением файла с любым названием и расширением — даже таким, которое запрещено в этой системе хранения.
Скрипт для создания подобных архивов
Для более глубокого изучения уязвимости я решил автоматизировать подмену имени файла внутри архива и написал два скрипта. MacKekZip.py меняет имя внутри архива в секции local file, а InvertedMacKekZip.py, соответственно, меняет имя файла в секции central dir.
Сценарии атак
Несмотря на то, что уязвимость кажется серьезной, придумать реальные сценарии атаки с ее использованием оказалось нетривиальной задачей. Я смог найти ей реальное применение в нескольких сценариях, которые расположил в порядке увеличения импакта.
1) Неполиткорректное название файла. Возможный удар по репутации высокопоставленных сотрудников компании, например топ-менеджера. Сотрудник может получить от недоброжелателя специальный архив с неким документом; он проверяет файл на своем домашнем компьютере под Windows и не видит подвоха. Распаковав архив на macOS во время важной встречи, он внезапно обнаруживает, что имя файла совсем не то, что раньше, хотя архив тот же. Имя файла может быть оскорбительным для участников встречи и сотрудник может попасть в неловкую ситуацию.
2) Другое расширение файла. Непосредственная возможность сохранения zip-архивов, содержащих опасные файлы, в защищенном контуре, минуя системы проверки расширения файла внутри архива. Так как контур защищенный, пользователи всецело доверяют находящимся в нем файлам. При распаковке такого архива на macOS пользователь может получить вредоносный файл, который пользователям других систем будет отображаться в виде простого текстового файла с непонятным содержимым.
3) Полиглоты. Существуют такие файлы, которые содержат в себе свойства нескольких типов файлов. Например, такой полиглот можно открыть как html-страничку и как pdf-документ, не меняя структуру файла, а лишь программу, которая отображает его содержимое. Использование полиглотов для эксплуатации данной уязвимости открывает большие возможности: можно сделать так, что на macOS полиглот распаковывается с расширением html, а на других платформах — с pdf. Таким образом, если добавить в html и pdf разные ссылки на подконтрольный злоумышленнику сервер, можно отслеживать, на какой платформе и в какое время был открыт вредоносный архив, и так собирать информацию об использовании устройств на базе macOS в конкретной компании. Возможности полиглотов безграничны, подробнее о них можно почитать, например, тут.
4) Одна из удивительных возможностей использования уязвимости — это подмена содержимого файла, хотя, казалось бы, менять можно только имя файла. Допустим, есть два файла: один — это файл с безопасными зависимостями requirements.txt, а второй — с вредоносными зависимостями evil_requirements.txt. При изменении архива, содержащего эти два файла, можно заменить имя первого файла в local file на ./tmp/
. Таким образом, вместо текстового файла создастся директория, на которую никто не обратит внимания. Имя же второго файла можно заменить на requirements.txt в local file, а в central dir — на ./tmp/
. Тогда разработчик на macOS может собрать проект используя полученный в архиве вредоносный файл с зависимостями, в то время как у другого разработчика на linux проект соберется с правильными зависимостями. Оба разработчика не будут подозревать, что на самом деле у них — разные файлы с зависимостями, а в архиве два таких файла вместо одного.
Шикарный 0-day (нет)
В процессе исследования мне стало интересно: неужели ни один исследователь не попробовал такой простой трюк за столько лет существования стандарта zip? Оказывается, что подобная уязвимость была найдена еще в 2009 году израильским экспертом Danor Cohen (https://securityaffairs.co/wordpress/23623/hacking/winrar-zero-day.html). Старая версия WinRAR позволяла выполнять произвольный код во время открытия файла из архива. Виной всему была точно такая же подмена имени файла в local file архива.
Пример эксплуатации RCE в WinRAR
Реальный импакт уязвимости
Казалось бы, точно такая же уязвимость, как и 12 лет назад, отличный 0-day (4425-day). Однако само устройство стандартной утилиты для работы с архивами в macOS не позволяет в полной мере раскрыть потенциал уязвимости, так как:
Пользователь не видит содержимое архива до распаковки — соответственно, он изначально не знает, с каким расширением его ждет файл после нее.
Во время распаковки архива не происходит мгновенное выполнение файла — вместо этого он сразу сохраняется в текущую директорию файловой системы.
Не высвечивается окошечко PWNED, а значит и возможности эксплуатации сводятся к минимуму.
Реакция Apple
Данная особенность была найдена еще в марте, было написано письмо в команду Apple, но специалисты корпорации не нашли в ней ничего опасного. Однако, они не исключили возможность исправления уязвимости когда-то в будущем. Прошло уже больше полугода, архиватор не починили, и я решил рассказать сообществу об этой находке, которая, как я все еще считаю, может потенциально нести в себе угрозу для конечных пользователей. Однако, самое интересное еще впереди, так как на этом проблемы macOS не заканчиваются, но о них я расскажу в другой раз.