Безопасность Parse в iOS приложении

Parse — прекраснейший BaaS, позволяющий в кратчайшее время поднять полноценную серверную инфраструктуру для мобильного приложения. Возможно, именно из-за этой простоты многие разработчики и забывают о появляющихся проблемах безопасности и открывающихся уязвимостях.

p1

Для тех, кто не знаком с сервисом, совершим небольшой экскурс в то, что он собой представляет. Parse предоставляет разработчику такие сервисы, как облачное хранилище данных, рассылку push-уведомлений, написание собственного API, сбор статистики, crash-логов и многое другое. В рамках этого исследования нас интересует именно хранилище данных, называемое Cloud Core.
Все данные в Parse хранятся в классах (по сути — таблицах), между записями которых можно устанавливать полноценные связи.

p2

Для каждого из классов настраиваются клиентские права доступа, влияющие на возможность поиска, добавления новых записей, изменения существующих, и прочее. По умолчанию все действия разрешены. Конечно же, как водится, большинство разработчиков, один раз настроив нужные им таблицы, забывают о настройке клиентских разрешений.

p3

Тесно столкнувшись на одном из рабочих проектов с Parse и повозившись с настройкой ACL, я решил поиграться и с чужими приложениями. Объект для исследования я выбрал прямо на parse.com/customers. Им стал Cubefree — сервис поиска мест для коворкинга.

Для подключения к аккаунту Parse в iOS приложении используется связка из двух ключей — Application ID и Client Key. Чтобы выполнять какие-либо действия над данными в Cloud Core первым делом нужно узнать эти данные. При помощи шикарной утилиты idb, автоматизирующей многие рутинные действия при пентестинге, расшифруем исполняемый файл приложения. Пока идет процесс, проверим NSUserDefaults — вполне вероятное место хранения интересующих нас ключей.
p4
В этом случае все вполне безобидно — никаких конфиденциальных данных. Вернемся к расшифрованному бинарнику и скормим его дизассемблеру Hopper, специализирующемуся на реверс-инжиниринге приложений, написанных на Objective-C. Поиск ключей начнем с метода application:didFinishLaunchingWithOptions: в AppDelegate. Одна из замечательных возможностей Hopper — представление метода в виде псевдокода, который значительно понижает порог понимания расшифрованного кода.
p5

Как и ожидалось, подключение к аккаунту Parse происходит именно здесь. Используя эти ключи, мы и будем анализировать структуру данных приложения и права доступа к ним.

Следующий шаг — поиск названий таблиц Parse. На самом деле, где их искать, становится понятно из этого же скриншота — сразу за подключением к серверу следует вызов методов registerSubclass у нескольких классов-наследников корневого PFObject. У каждого из них обязательно должен быть имплементирован метод parseClassName, отдающий интересующее нас имя таблицы на сервере.

p6

Изучим структуру каждого из полученных таким образом классов:

Тем не менее, знания одной лишь структуры недостаточно. Чтобы понять, каким образом мы можем повлиять на работу приложения, нужно определить права доступа ко всем классам Parse. Делается это достаточно просто — мы всего лишь выполняем соответствующие различным разрешениям запросы к серверу и анализируем их результат. Для упрощения этих рутинных действий я написал простенькую утилиту Parse Revealer, которая автоматически определяет уровни доступа ко всем известным классам.
p7
На основании полученных нами данных можем построить таблицу:

p9

Как видно из полученных прав доступа, разработчики реализовали определенную политику безопасности, но, все ж таки, недостаточную. Покажем, каких результатов можно добиться, поиграв с классом ChatMessage.

Наиболее очевидная уязвимость — в любом из открытых чатов можно изменить как свои, так и чужие сообщения. После выполнения этого кода милое приветствие превращается в хабрасуицид:

p8

Аналогичным образом можно добавлять и новые сообщения, достаточно лишь предоставить новому PFObject корректный chatId. Но стоит отметить, что Delete, установленный в false, не даст нам удалить ни одного из созданных объектов.

Гораздо более серьезная уязвимость заключается в некорректном маппинге данных, полученных из Parse. Если у свежесозданного объекта ChatMessage будет отсутствовать поле sender — приложение крашится. Таким образом, ничто не мешает нам пробежаться по всем когда либо созданным часам, добавить в них невалидное сообщение — и приложение будет вылетать у всех пользователей. Это уже чревато низкими рейтингами в App Store, оттоком пользователей и неудачей проекта в целом.
Подобные уязвимости есть и у остальных классов — но они уже находятся за рамками текущего исследования.

Что касается обеспечения безопасности — здесь все достаточно прозрачно. Нужно следовать лишь нескольким правилам:

  • Всегда настраивайте уровни доступа для всех созданных классов.
  • Для создаваемых пользователем данных используйте ACL, позволяя изменять их только определенному кругу лиц.
  • Если клиенту необходимо изменять только одно из свойств (к примеру, флаг unread) — стоит задуматься о выделении его в отдельную таблицу. Таким образом, можно будет обойти возможность изменения других параметров объекта.
  • Не стоит полагаться на то, что Parse всегда будет отдавать валидные данные — не забывайте встраивать соответствующие проверки.
  • Не забывайте и о том, что, теоретически, applicationId и clientKey могут быть доступны любому злоумышленнику, и продумывайте политику безопасности, основываясь на этом знании.
  • Предыдущее правило не означает, что нужно полностью забыть об обфускации строк в коде :)
  • В особо сложных случаях не стесняйтесь использовать Cloud Code.

Если в этом исследовании вы увидите черты и своих приложений, не стоит ругать Parse — как я уже говорил, это отличный сервис, минимизирующий затраты на создание серверной части приложения. А все рассмотренные уязвимости лежат только на ответственности разработчиков приложения.

Полезные ссылки:

Другие материалы, посвященные обеспечению безопасности iOS приложений: