Безопасность 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: 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 приложений:
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 15

      0
      «Не забывайте и о том, что, теоретически, applicationId и clientKey могут быть доступны любому злоумышленнику»

      Вопрос такой. А разве можно как-то сделать так, чтобы эти данные были НЕ доступны злоумышленнику?
        0
        Промахнулся веткой, ответил ниже.
        0
        The first thing you need to understand is that your client key is not a security mechanism. It’s not even intended to serve as such. Your client key is shipped as a part of your app.

        Полностью скрыть, конечно, нельзя — но для усложнения доступа можно попытаться обфусцировать эти строки, или, при сильном желании, немного поиграться со стеганографией. Часть злоумышленников это сможет оттолкнуть и направить на поиски более легкой добычи.
          +2
          Отличная статья, спасибо!

          Я как раз один из таких разработчиков, который откладывает настройку безопасности на потом. Надо сделать для себя соответствующие выводы.

          PS: для компиляции проекта надо OS X 10.10 :(
          /ParseRevealer-master/ParseRevealer/Base.lproj/Main.storyboard: The document «Main.storyboard» could not be opened. OS X 10.10 or higher is required.
            0
            Да, Storyboards в приложения для OS X добавили только в 10.10. Добавляйте issue, поправлю в ближайшее время.
            +1
            К этим данным же можно получить доступ только в случае физического доступа к девайсу, который не закрыт паролем. Так?
              0
              К ним можно получить доступ с любого устройства, на которое установлено приложение. То есть, если оно есть в App Store, то никаких ограничений нет.
                0
                clientKey – твой либо с чужого девайса, разве нет?
                  0
                  clientKey устанавливается в настройках аккаунта Parse и един для всех установок.
                    +1
                    Теперь я понял, круто однако.
              +1
              спасибо за пищу для размышлений, но не стоит путать теплое с мягким, если я правильно понимаю — у разработчиков того-же чата в примере — не было выбора кроме как разрешить редактирование всем пользователям в админке, иначе пользователь не сможет редактировать свои сообщения. то-же касаемо остальных настроек безопасности.

              Просто помимо глобальных настроек безопасности есть ACL для отдельных объектов, и с ними и нужно работать, тогда никто, без masterKey ничего за пределами своего аккаунта не сделает. А вы, я так понял, проверяли только объекты принадлежащие вам.

              Так что глобальные настройки, описанные в статье погоды не делают, а ACL для отдельных объектов не рассмотрены, с выводами «безопасность нужна» более-менее согласен, а вот методикой исследования не очень проникся

              И еще спасибо за утилиту, но напишите пожалуйста в ней предупреждение, что она пишет в базу, это не совсем очевидно и это будет более user developer friendly, а то пришлось сперва подумать откуда всплыли пустые записи в продакшн базе.
                0
                Отвечу по пунктам:
                1. Вообще в этом приложении редактирование сообщений чата не предусмотрено — как раз таки возможность этого и является одной из уязвимостей. Меняться должен только флаг unread при просмотре сообщения. Здесь, как раз таки, могли бы помочь user-based ACL, либо вынесение этого и/или других флагов в отдельную изменяемую таблицу.
                2. Даже с отдельными ACL для каждого из объектов не решить рассмотренные здесь проблемы, не меняя глобальных прав доступа — как минимум, ничто не мешает создавать новые объекты, присваивать им определенный chatId, и наблюдать, как приложение будет крешиться. Но это безусловно необходимый шаг, о чем я и написал в секции рекомендаций :)
                3. Статья задумывалась не как гайд по правильной конфигурации, а как proof-of-concept этого вектора атаки на исследуемое приложение.

                А предупреждение обязательно добавлю :)
                0
                Немного оффтоп. Пытался пользоваться Cloud Code, но простые скрипты вылетают по таймауту в 3 секунд (сами скрипты почти идентичны скриптам-примерам в справке самого Parse). Пришлось отказаться от него, хотя было бы удобно…
                Вопрос: это на халявном аккаунте такая плохая производительность или вообще?
                  0
                  ну скрипты по идее должны быстро выполняться, но там куча других ограничений, например жестокий лимит на количество операций count

                  а для 3 секунды и больше лучше background job использовать — у них лимиты по времени гораздо больше но точно не скажу
                    0
                    Спасибо, попробую.

                Only users with full accounts can post comments. Log in, please.