Безопасность Parse в iOS приложении
Parse — прекраснейший BaaS, позволяющий в кратчайшее время поднять полноценную серверную инфраструктуру для мобильного приложения. Возможно, именно из-за этой простоты многие разработчики и забывают о появляющихся проблемах безопасности и открывающихся уязвимостях.Для тех, кто не знаком с сервисом, совершим небольшой экскурс в то, что он собой представляет. Parse предоставляет разработчику такие сервисы, как облачное хранилище данных, рассылку push-уведомлений, написание собственного API, сбор статистики, crash-логов и многое другое. В рамках этого исследования нас интересует именно хранилище данных, называемое Cloud Core.Все данные в Parse хранятся в классах (по сути — таблицах), между записями которых можно устанавливать полноценные связи.
Для каждого из классов настраиваются клиентские права доступа, влияющие на возможность поиска, добавления новых записей, изменения существующих, и прочее. По умолчанию все действия разрешены. Конечно же, как водится, большинство разработчиков, один раз настроив нужные им таблицы, забывают о настройке клиентских разрешений.
Тесно столкнувшись на одном из рабочих проектов с Parse и повозившись с настройкой ACL, я решил поиграться и с чужими приложениями. Объект для исследования я выбрал прямо на parse.com/customers. Им стал Cubefree — сервис поиска мест для коворкинга.
Для подключения к аккаунту Parse в iOS приложении используется связка из двух ключей — Application ID и Client Key. Чтобы выполнять какие-либо действия над данными в Cloud Core первым делом нужно узнать эти данные. При помощи шикарной утилиты idb, автоматизирующей многие рутинные действия при пентестинге, расшифруем исполняемый файл приложения. Пока идет процесс, проверим NSUserDefaults — вполне вероятное место хранения интересующих нас ключей.В этом случае все вполне безобидно — никаких конфиденциальных данных. Вернемся к расшифрованному бинарнику и скормим его дизассемблеру Hopper, специализирующемуся на реверс-инжиниринге приложений, написанных на Objective-C. Поиск ключей начнем с метода application: didFinishLaunchingWithOptions: в AppDelegate. Одна из замечательных возможностей Hopper — представление метода в виде псевдокода, который значительно понижает порог понимания расшифрованного кода.
Как и ожидалось, подключение к аккаунту Parse происходит именно здесь. Используя эти ключи, мы и будем анализировать структуру данных приложения и права доступа к ним.
Следующий шаг — поиск названий таблиц Parse. На самом деле, где их искать, становится понятно из этого же скриншота — сразу за подключением к серверу следует вызов методов registerSubclass у нескольких классов-наследников корневого PFObject. У каждого из них обязательно должен быть имплементирован метод parseClassName, отдающий интересующее нас имя таблицы на сервере.
Изучим структуру каждого из полученных таким образом классов:
PFQuery *query = [PFQuery queryWithClassName:@«ParseClassName»]; [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) { NSLog (@»%@», objects); }]; Тем не менее, знания одной лишь структуры недостаточно. Чтобы понять, каким образом мы можем повлиять на работу приложения, нужно определить права доступа ко всем классам Parse. Делается это достаточно просто — мы всего лишь выполняем соответствующие различным разрешениям запросы к серверу и анализируем их результат. Для упрощения этих рутинных действий я написал простенькую утилиту Parse Revealer, которая автоматически определяет уровни доступа ко всем известным классам.На основании полученных нами данных можем построить таблицу: Название класса Структура данных Права доступа ChatRoom chatId (String)user1 (User)user2 (User) GET: FalseFIND: TrueUPDATE: TrueCREATE: TrueDELETE: FalseADD FIELDS: True Checkin availableToShareTable (Bool)date (Date)invisible (Bool)statusCheckin (String)statusUser (String)user (User)workspace (Workspace) GET: TrueFIND: TrueUPDATE: TrueCREATE: TrueDELETE: TrueADD FIELDS: True ChatMessage chatId (String)Message (String)sender (User)unread (Bool) GET: FalseFIND: TrueUPDATE: TrueCREATE: TrueDELETE: FalseADD FIELDS: True Notification date (Date)sendUser (User)chekin (Cheking)status (Bool)type (Number)accepted (Bool) GET: TrueFIND: TrueUPDATE: TrueCREATE: TrueDELETE: FalseADD FIELDS: True Review date (Date)parkingStatus (Number)powerStatus (Number)soundStatus (Number)user (PFUser)wifiStatus (Number)workspace (Workspace) GET: TrueFIND: TrueUPDATE: FalseCREATE: TrueDELETE: FalseADD FIELDS: True Workspace address (String)cc (String)city (String)country (String)foursquareId (String)lat (String)lng (String)location (PFGeoPoint)name (String)postalCode (String)state (String) GET: TrueFIND: TrueUPDATE: TrueCREATE: TrueDELETE: FalseADD FIELDS: True Как видно из полученных прав доступа, разработчики реализовали определенную политику безопасности, но, все ж таки, недостаточную. Покажем, каких результатов можно добиться, поиграв с классом ChatMessage.Наиболее очевидная уязвимость — в любом из открытых чатов можно изменить как свои, так и чужие сообщения. После выполнения этого кода милое приветствие превращается в хабрасуицид:
PFQuery *query = [PFQuery queryWithClassName:@«ChatMessage»]; [query whereKey:@«message» equalTo:@«Привет, Хабр!»];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) { PFObject *object = [objects firstObject]; object[@«message»] = @«Хабр, я тебя ненавижу!»; [object saveInBackground]; }]; Аналогичным образом можно добавлять и новые сообщения, достаточно лишь предоставить новому PFObject корректный chatId. Но стоит отметить, что Delete, установленный в false, не даст нам удалить ни одного из созданных объектов.
Гораздо более серьезная уязвимость заключается в некорректном маппинге данных, полученных из Parse. Если у свежесозданного объекта ChatMessage будет отсутствовать поле sender — приложение крашится. Таким образом, ничто не мешает нам пробежаться по всем когда либо созданным часам, добавить в них невалидное сообщение — и приложение будет вылетать у всех пользователей. Это уже чревато низкими рейтингами в App Store, оттоком пользователей и неудачей проекта в целом.Подобные уязвимости есть и у остальных классов —, но они уже находятся за рамками текущего исследования.
Что касается обеспечения безопасности — здесь все достаточно прозрачно. Нужно следовать лишь нескольким правилам:
Всегда настраивайте уровни доступа для всех созданных классов. Для создаваемых пользователем данных используйте ACL, позволяя изменять их только определенному кругу лиц. Если клиенту необходимо изменять только одно из свойств (к примеру, флаг unread) — стоит задуматься о выделении его в отдельную таблицу. Таким образом, можно будет обойти возможность изменения других параметров объекта. Не стоит полагаться на то, что Parse всегда будет отдавать валидные данные — не забывайте встраивать соответствующие проверки. Не забывайте и о том, что, теоретически, applicationId и clientKey могут быть доступны любому злоумышленнику, и продумывайте политику безопасности, основываясь на этом знании. Предыдущее правило не означает, что нужно полностью забыть об обфускации строк в коде:) В особо сложных случаях не стесняйтесь использовать Cloud Code. Если в этом исследовании вы увидите черты и своих приложений, не стоит ругать Parse — как я уже говорил, это отличный сервис, минимизирующий затраты на создание серверной части приложения. А все рассмотренные уязвимости лежат только на ответственности разработчиков приложения.Полезные ссылки:
Другие материалы, посвященные обеспечению безопасности iOS приложений: