Parse – прекрасная технология на основе модели BaaS (Backend as a service), позволяющей в кратчайшие сроки настроить инфраструктуру бэк-энда для мобильного приложения. С другой стороны, простота «расслабила» многих разработчиков, заставить позабыть о проблемах с безопасностью и уязвимостях.
Автор: Егор Толстой
Parse – прекрасная технология на основе модели BaaS (Backend as a service), позволяющей в кратчайшие сроки настроить инфраструктуру бэк-энда для мобильного приложения. С другой стороны, простота «расслабила» многих разработчиков, заставить позабыть о проблемах с безопасностью и уязвимостях.
Рисунок 1: Интерфейс Parse
Для тех, кто ничего не знает о Parse, позволю себе пару вводных слов. Parse значительно расширяет возможности разработчика мобильных приложений. Например, можно без особого труда подключить облачное хранилище, пуш-уведомления, сбор статистики и крэш репортов, систему хранения кода, фоновые задания и многие другие фишки. В этой статье мы будем использовать только облачное хранилище Cloud Core.
Вся информация в Cloud Core хранится в так называемых специальных классах (custom classes), представляющих собой обычные таблицы базы данных.
Рисунок 2: Интерфейс для работы с таблицами
Для каждого класса можно установить различные права доступа: GET, FIND, UPDATE, CREATE, DELETE и ADD FIELDS. По умолчанию, все права доступа идут с атрибутом Public (то есть всем все разрешено), и естественно большинство разработчиков забывает о правах доступа после добавления и настройки таблиц.
Рисунок 3: Настройка прав для конкретной таблицы
В одном из своих проектов я довольно плотно работал с Parse и в частности с настройкой прав доступа, после чего мне стало интересно, как у других разработчиков организовано взаимодействие с учетными записями, зарегистрированными в Parse. Подопытным кроликом выступил сервис для поиска офисов Cubefree.
Для соединения с аккаунтом из мобильного приложения используется пара ключей: Application ID и Client Key. Чтобы как-то работать с таблицами в Cloud Core, необходимо заполучить эти ключи. Вначале расшифруем приложение при помощи idb, незаменимого инструмента в арсенале пентестера. Пока приложение расшифровывается, заглянем NSUserDefaults, где часто неграмотные разработчики хранят ключи.
Рисунок 4: Содержимое NSUserDefaults
Как видно из рисунка выше, в NSUserDefaults не хранится никаких конфиденциальных сведений. Вернемся к расшифрованному приложению и выполним дизассемблирование при помощи Hopper (известная утилита, хорошо подходящая для реверс-инжиниринга приложений, написанных на Objective-C). Наш квест по поиску ключей начнется с метода application:didFinishLaunchingWithOptions, принадлежащего App Delegate. В Hopper любая процедура может отображаться в виде псевдокода, что заметно облегчает изучение логики работы приложения.
Рисунок 5: Псевдокод метода application:didFinishLaunchingWithOptions
Как и ожидалось, подключение к Parse начинается отсюда. Далее мы будем анализировать структуру хранения данных и права доступа.
Для начала выясним имена таблиц. На Рисунке 5 мы видим множество вызовов метода registerSubclass. Классы, к которым принадлежит метод registerSubclass, являются дочерними элементами класса PFObject. У каждого из этих классов есть метод parseClassName, который возвращает имя таблицы, хранящейся в Parse.
Рисунок 6: Псевдокод метода parseClassName
Проанализируем структуру таблиц:
https://gist.github.com/igrekde/cb8e2c12408715c9f739#file-parse-security-1
Знания структуры классов (или таблиц) не достаточно. Чтобы понять, как мы можем повлиять на логику работы приложения, необходимо также проанализировать права доступа ко всем классам. Эта задача решается довольно просто. Нам необходимо выполнить пару запросов и проанализировать результаты. Я написал небольшую утилиту Parse Revealer, которая берет на себя всю рутинную работа и выводит права доступа ко всем известным классам.
Рисунок 7: Интерфейс утилиты Parse Revealer
Сведем в единую таблицу все полученные сведения.
Рисунок 8: Структура таблиц и права доступа
Изучив перечень прав доступа, мы понимаем, что разработчик приложения пытался реализовать некую политику безопасности, однако этого недостаточно. Рассмотрим, чего можно достичь посредством манипуляций с классом ChatMessage.
Наиболее очевидная уязвимость заключается в том, что злоумышленник может модифицировать текст любого сообщения в любой комнате. При помощи нижеуказанного кода элегантно изменяем тон беседы:
https://gist.github.com/igrekde/cb8e2c12408715c9f739#file-parse-security-2
Рисунок 9: Интерфейс чата
Мы также можем добавлять новые сообщения в любую комнату, доставив новый объект PFObject с корректным chatId. Однако мы благородные пентестеры и обратим внимание на тот факт, что сообщения нельзя удалять из-за повышенной мнительности разработчика :)
Более серьезная уязвимость заключается в некорректно реализованном алгоритме мапирования данных. Когда у объекта ChatMessage в поле отправитель (sender) - пустота, Cubefree аварийно завершается. Таким образом, становится возможным зациклить все комнаты, сформировав некорректный ChatMessage – и если кто-то откроет окно чата, приложение упадет. Подобный расклад чреват плохим рейтингом в App Store, оттоком пользователей и, в конечном счете, полным провалом проекта.
Существует еще несколько уязвимостей в других классах, но эта тема выходит за рамки данной статьи.
Чтобы ваши приложения были безопасными, следуйте простым правилам:
Если после прочтения статьи вы нашли бреши в своих приложениях, не обвиняйте Parse. Это отличный сервис, минимизирующий затраты на разработку бэк-энда. Все найденные уязвимости полностью находятся в зоне ответственности разработчика.
Ссылки