Pull to refresh

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

Reading time5 min
Views13K


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: False
FIND: True
UPDATE: True
CREATE: True
DELETE: False
ADD FIELDS: True
Checkin availableToShareTable (Bool)
date (Date)
invisible (Bool)
statusCheckin (String)
statusUser (String)
user (User)
workspace (Workspace)
GET: True
FIND: True
UPDATE: True
CREATE: True
DELETE: True
ADD FIELDS: True
ChatMessage chatId (String)
Message (String)
sender (User)
unread (Bool)
GET: False
FIND: True
UPDATE: True
CREATE: True
DELETE: False
ADD FIELDS: True
Notification date (Date)
sendUser (User)
chekin (Cheking)
status (Bool)
type (Number)
accepted (Bool)
GET: True
FIND: True
UPDATE: True
CREATE: True
DELETE: False
ADD FIELDS: True
Review date (Date)
parkingStatus (Number)
powerStatus (Number)
soundStatus (Number)
user (PFUser)
wifiStatus (Number)
workspace (Workspace)
GET: True
FIND: True
UPDATE: False
CREATE: True
DELETE: False
ADD 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: True
FIND: True
UPDATE: True
CREATE: True
DELETE: False
ADD 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 приложений:
Tags:
Hubs:
Total votes 21: ↑19 and ↓2+17
Comments15

Articles