Основные практики обеспечения безопасности iOS-приложений



    При разработке любого мобильного приложения, обрабатывающего пользовательские данные, важно уделить внимание безопасности. Особенно остро этот вопрос стоит для приложений, где фигурируют ФИО, номера телефонов, паспортов и другая личная информация. Наша компания разрабатывала и продолжает развивать несколько проектов такого рода, в частности приложения для клиентов российских банков. На основе этого опыта мы выработали набор требований безопасности, которым руководствуемся. Естественно, каждый год появляются новые технологии и возможности, а вместе с ними — новые особенности поведения и уязвимости. В этой статье мы зафиксировали основные пункты обеспечения безопасности iOS-приложений, актуальные на начало 2018 года.

    Хранение данных


    iOS всегда славилась своей защищенностью и вниманием к информационной безопасности. Тем не менее за время существования этой ОС было выявлено несколько серьезных уязвимостей, из-за которых происходили утечки пользовательских данных. Это лишний раз напоминает о том, что слишком много мер по обеспечению безопасности не бывает и нельзя во всем надеяться на систему. Чем меньше информации остается на диске после использования приложения, тем лучше.

    Поэтому рекомендуется хранить только те данные, без которых обойтись невозможно. Если среди них есть персональная информация пользователя — она хранится исключительно в Keychain. Если же необходимо использовать полноценную базу данных, такую как Core Data или Realm, она обязательно шифруется.

    Информация, доступная в поиске через Spotlight, также не должна содержать данные о пользователе.

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

    В дополнение к этому можно отключить кэширование веб-запросов, потому что их содержимое также сохраняется на диск. Сделать это можно следующим образом:

    let cache = URLCache(
        memoryCapacity: 0,
        diskCapacity: 0,
        diskPath: nil)
    URLCache.shared = cache

    А удалить уже закэшированные запросы можно одной строчкой:

    URLCache.shared.removeAllCachedResponses()

    Соединение с сервером


    До выхода iOS 9 приложения могли свободно делать запросы к любому адресу по протоколу HTTP. В этом случае данные передавались по сети в незашифрованном виде, и злоумышленникам не составляло особого труда отслеживать содержимое запроса. Начиная с iOS 9 Apple решила ввести жесткие требования к сетевым соединениям и разработала правила App Transport Security (ATS), согласно которым все запросы в Интернет должны производиться посредством протокола HTTPS и шифроваться с помощью TLS 1.2 (с поддержкой forward secrecy). Операционная система сама по умолчанию следует этим требованиям, поэтому необходимо лишь их соблюдение со стороны сервера.

    Разработчики также получили возможность указывать параметры подключения как для всех соединений, так и для запросов к конкретным доменам. Все они прописываются в файле Info.plist приложения. Здесь основным параметром является NSAllowsArbitraryLoads, отключающий все правила ATS (по умолчанию отключен). Если его включить, то Apple при проведении ревью приложения в App Store запросит веское обоснование этого действия. Может показаться странным наличие этого флага, но на практике он используется совместно с другим — NSAllowArbitraryLoadsInWebContent, отключающим ATS только внутри WebView. Он часто оказывается необходим, поскольку мы не можем гарантировать, что открываемые в приложении страницы браузера соответствуют всем требованиям безопасности от Apple. Появилась эта настройка только в iOS 10, поэтому, чтобы веб-страницы открывались и на устройствах с iOS 9, приходится включать флаг NSAllowsArbitraryLoads. А вот для всех последующих версий iOS значение NSAllowsArbitraryLoads будет игнорироваться при наличии NSAllowArbitraryLoadsInWebContent.

    SSL-пиннинг


    Даже при использовании HTTPS-соединений остается возможность просмотра данных третьими лицами при обмене с сервером. Например, в публичных сетях злоумышленник мог бы отслеживать трафик с помощью атаки Man-in-the-middle, в которой он становится посредником между приложением и сервером. Бороться с этим можно с помощью SSL-пиннинга. На практике это означает, что приложению известен SSL-сертификат сервера, используемый для HTTPS-соединения, и другим сертификатам оно не доверяет. При получении от сервера неизвестного сертификата (как в случае с атакой Man-in-the-middle) соединение обрывается. Осуществить пиннинг можно по-разному: хранить в приложении сам файл сертификата, его хеш или публичный ключ. Если для сетевых соединений используется библиотека Alamofire, можно воспользоваться классом ServerTrustPolicyManager, который поддерживает все варианты пиннинга.

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

    Авторизация в приложении


    Во многих приложениях авторизация происходит с помощью ввода 4-6 значного пин-кода, придуманного пользователем во время регистрации. Естественно, хранить этот код в чистом виде ни на устройстве, ни на сервере нельзя.

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

    В лучшем случае в базе данных приложения должна храниться только эта соль. Но в приложениях часто есть возможность входа с помощью Touch ID и Face ID, когда пользователь попадает в свой аккаунт, предоставляя только биометрические данные. В таких ситуациях приходится хранить в Keychain и сам пин-код или получившийся из него хеш.

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

    После авторизации на сервере, мы получаем токен, по которому совершаем все последующие запросы на сервер. Токен имеет ограниченный срок действия и через какое-то время «протухает». В этом случае пользователь снова должен ввести пин-код, чтобы получить новый токен. Действие токена может закончиться как на сервере (если в течение 10-15 минут по этому токену не совершалось запросов), так и в приложении (если оно было свернуто достаточно долго, около 2 минут).

    Токен не хранится между сессиями, и при каждом новом запуске приложения его необходимо получать через ввод пин-кода.

    Touch ID и Face ID




    Биометрическая аутентификация существенно упрощает вход в мобильные приложения. Apple приводит статистику, согласно которой шанс совпадения отпечатка пальца пользователя с отпечатком другого человека равен 1 к 50 000, в то время как шанс совпадения сканов лиц — 1 к 1 000 000. Все связанные с этим вычисления производятся в сопроцессоре Secure Enclave, который полностью изолирован от операционной системы (подробнее о технологии можно прочитать здесь). Из-за этого получить доступ к биометрическим данным пользователя или воспользоваться ими невозможно, поэтому разработчику приложения заботиться об этом не приходится. Тем не менее, есть некоторые интересные моменты, связанные с этими технологиями.

    Например, злоумышленник мог бы узнать пин-код от самого телефона, добавить свой отпечаток пальца в список отпечатков, известных системе, и авторизовываться по нему в приложении, не зная пин-код. Для решения этой проблемы операционная система предоставляет хеш под названием evaluatedPolicyDomainState, описывающий текущий набор отпечатков пальцев. Этот хеш можно сохранить на диск при первой успешной авторизации в приложении и при последующих авторизациях проверять, отличается ли текущее значение от сохраненного. Если они различны — база данных отпечатков была изменена, вход по Touch ID отключается, и пользователю необходимо повторно ввести пин-код приложения для сохранения нового значения хеша. То же относится и к Face ID.

    Проверка на Jailbreak


    Можно по-разному относиться к политике Apple по ограничению пользовательских прав на контроль над смартфоном, но факт остается фактом: если пользователь решил поставить Jailbreak, про большинство аспектов безопасности iOS можно забыть. Любое установленное приложение потенциально может получить доступ ко всей хранимой информации.
    В настоящее время Jailbreak стал менее распространенным, в том числе благодаря тому, что с выходом каждой новой версии iOS сделать его становится все сложнее.

    При обнаружении Jailbreak можно накладывать жесткие ограничения: блокировать доступ к отдельным частям приложения или даже запрещать им пользоваться.
    Осуществить проверку на Jailbreak можно разными способами. Например, проверять наличие пакета Cydia на устройстве или возможность записи за пределами песочницы приложения.

    func hasCydiaBundle() -> Bool {
        let filePath = "/Applications/Cydia.app"
        return FileManager.default.fileExists(atPath: filePath)
    }
        
    func writeOutSandbox() -> Bool {
        do {
            let filePath = "/private/test.txt"
            let fileContents = "test"
            try fileContents.write(toFile: filePath, atomically: true, encoding: .utf8)
            try? FileManager.default.removeItem(atPath: filePath)
            return true
        } catch {
            return false
        }
    }

    Стоит добавить, что подобные способы не могут полностью защитить приложение от использования на устройстве с Jailbreak, но значительно это усложняют.

    Антифрод


    Даже если злоумышленник получил полный доступ к телефону или аккаунту пользователя, должны существовать способы перекрыть ему возможность совершать операции в приложении. Для этого при авторизации важно отправлять на сервер информацию об устройстве (идентификатор, модель, версию iOS) — если пользователь потерял доступ к своему телефону, устройство можно будет внести в черный список на сервере.

    Также при использовании приложения можно отправлять на сервер данные о геолокации пользователя (при условии, что он дал к ней доступ). Если операции производятся из нетипичных мест, есть возможность приостановить обслуживание до тех пор, пока пользователь не подтвердит, что действия совершаются действительно им.

    Все важные изменения настроек и операции обязательно должны подтверждаться с помощью СМС-кода. Количество попыток ввода кода тоже должно быть ограничено.

    Ввод данных


    Важно обеспечить безопасность в том числе и при вводе информации внутри приложения.
    Например, в большинстве текстовых полей рекомендуется отключать возможность автозаполнения (свойство UITextField autoCorrectionType). Если этого не сделать, вводимые данные (которые могут быть персональными) будут индексироваться операционной системой и станут появляться в качестве вариантов для автозаполнения в других приложениях. А все текстовые поля, в которых вводятся пароли, конечно же маскируются и не поддерживают возможность копирования/вставки.

    Для обхода потенциально существующих на устройстве кейлоггеров ввод пин-кода при авторизации производится не с помощью системной клавиатуры, а посредством кнопок-цифр на экране.

    Другие особенности iOS


    Когда пользователь сворачивает приложение, операционная система делает скриншот экрана, который затем отображается в списке свернутых приложений. Этот скриншот хранится в папке на смартфоне, и важно предусмотреть вариант, при котором на скриншоте могут оказаться личные данные. Сделать это можно разными путями: заблюрить содержимое экрана или поставить «шторку» поверх него в момент сворачивания приложения, главное, чтобы на скриншоте нельзя было рассмотреть содержимое экрана.

    Для выполнения важных операций не рекомендуется использовать WebView, в котором ранее уже были найдены различные уязвимости, связанные с выполнением кода Javascript. Тем не менее, в некоторых приложениях сервисов без веб-страниц не обойтись. В таких случаях тоже рекомендуется использовать SSL-пиннинг с набором доверенных сертификатов сторонних сайтов, который приложение может получать от сервера.

    Защита кода


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

    void disableDebugger() 
    {
        void *dllHandle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
        ptrace_ptr_t ptrace_method_pointer = dlsym(dllHandle, "ptrace");
        ptrace_method_pointer(PT_DENY_ATTACH, 0, 0, 0);
        dlclose(dllHandle);
    }
    
    static int __unused isDebuggerPresent(void)
    {
        int name[4];
        struct kinfo_proc info;
        size_t infoSize = sizeof(info);
        
        info.kp_proc.p_flag = 0;
        
        name[0] = CTL_KERN;
        name[1] = KERN_PROC;
        name[2] = KERN_PROC_PID;
        name[3] = getpid();
        
        if (sysctl(name, 4, &info, &infoSize, NULL, 0) == -1) {
            perror("sysctl failure");
            exit(-1);
        }
        
        return ((info.kp_proc.p_flag & P_TRACED) != 0);
    }

    Если дебаггер найден, приложение аварийно завершает работу.

    С приложением также не должны поставляться дебаг-символы, а в настройках компиляции проекта выставлены все значения, рекомендованные Apple для сборок в AppStore.

    Если проект написан на Objective-C, можно использовать сторонние средства по обфускации кода, которые дополнительно усложнят реверс-инжиниринг. Для языка Swift такой необходимости нет, поскольку компилятор сам проводит обфускацию при компиляции в Release-режиме.

    Заключение


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

    Если вы хотите глубже ознакомиться с темой безопасностью мобильных приложений, стоит обратиться к самому популярному ресурсу на эту тему: OWASP Mobile Security Project.
    Также свои материалы по этому вопросу предоставляет компания Apple, например Security Development Checklist.
    Redmadrobot
    128.86
    №1 в разработке цифровых решений для бизнеса
    Share post

    Similar posts

    Comments 14

      0
      Неплохо! Планируете ли по Android платформе похожие материалы готовить?
        +2
        А вам правда нужен такой материал по Android? На хабре уже очень много подобного и поэтому мы даже не думали готовить такой материал. Давайте так, если вам нужен такой материал, то ставьте плюс в этот комментарий. По итогу решим.
        0
        Даже при использовании HTTPS-соединений остается возможность просмотра данных третьими лицами при обмене с сервером. Например, в публичных сетях злоумышленник мог бы отслеживать трафик с помощью атаки Man-in-the-middle, в которой он становится посредником между приложением и сервером.

        Чуть подробнее, пожалуйста. Я подозреваю, что такие атаки действительно есть, но фраза вызывает ощущение, что SSL вообще не защищает от MIM. И насколько хорошо работает в случае таких атак SSL-pinning?

          0
          Сам по себе SSL действительно не спасает от MIM, есть ряд методов с помощью которых можно провести такую атаку даже при HTTPS-соединении. Для защиты от этих сценариев как раз и используется пиннинг.
            0

            И все же, для непонятливых. Что это за атаки? Мне казалось, SSL как раз и сделан ради защиты от MITM. В голову приходит, только предварительная установка левого сертификата на устройство, но это никак не ложится на ваш сценарий подключения к публичным сетям. Ну ещё есть взлом корневых CA, но я им доверяю чуточку больше админа сервера.

              0
              www.roe.ch/SSLsplit — вот такая атака например. Противодействовать ей может только SSL pinning.
                +1

                Эта атака возможна, если злоумышленник является CA (certifying authority), которому доверяет жертва.
                Хотя это теоретически возможно в случае утечки ключей у легитимного CA или нарушения порядка выдачи сертификатов (и даже было на практике: раз, два) — это все же не такая частая ситуация.


                Может, вы это и имели в виду, конечно. Просто по тексту вашего комментария складывается ощущение, что каждый встречный может использую sslsplit / mitmproxy осуществить атаку MiTM на SSL — что совсем не так.

          0
          Спасибо за развернутую статью, но, поправьте меня, если я не прав: проверка на JailBreak слабовата. Суть JB не в установке Cydia, и не в попытке записи в корневые директории… Да, такая защита подойдет от среднестатистического JB-пользователя, но не от людей, которые просто раздвигают границы дозволенного, а потом так же думают о своей безопасности — меняя SSH пароли и выставлением своих прав на чтение-запись на папки и файлы…
            0
            Да, все верно, подобные проверки сработают только для среднестатистического юзера, но именно для него они и предназначены. Если человек настолько хорошо в этом разбирается, то он сам понимает все риски JB.
              0
              Раньше можно было установить несколько пакетов в сидии, которые скрывали наличие JB на устройстве. При наличии JB есть возможность зареверсить приложение, изменить исходный код и обратно собрать его на устройство – 100% защиты нет. Но процент пользователей с JB сейчас ничтожно мал и его нет на последние версии ОС.
              –2
              Спасибо за статью, но по моему личному мнению, статья очень поверхностная и не совсем понятно для защиты от чего написанная. При получении доступа к устройству жертвы и при возможности произвести джейлбрейк и при наличии обкатанной технологии/наличии знаний о устройстве конкретного приложения(предварительно полученные)- вы не не можете абсолютно ничего защитить, абсолютно все можно заменить, переписать, зафейкать и обмануть, вы можете только применить меры, которые усложнят(увеличат время) процессов предварительного сбора информации и непосредственного произведения неправомерного действия, получения личной информации. Единственный адекватный способ защиты- делать тонкие(thin) клиенты и всю логику перемещать на сервер, что собственно почти все и делают. Тут уже остаются только ошибки и уязвимости в протоколе обмена, на логике сервера и тому подобное, что перемешает ответственность на сторону разработчиков сервера, где ей и место и где есть уже промышленные стандарты и по безопасности и по нахождению уязвимостей, такие стандарты, кстати, уже почти есть и в мобильной разработке, тема OWASP- тоже очень слабо развита, приведены не все ссылки на их инструменты и проекты. Таким образом, лично мое мнение, что статья о том как производить абсолютно бесполезные действия(я ни в коем случае не предлагаю передавать через GET и http весь трафик и хранить пароли в плисте, вместе с номерами кредиток), но необходимо понимание, что является бесполезным, а что действительно может значительно усложнить реверс- инжениринг, подмену сертификатов, изменение данных и тому подобные процессы пентеста, а для этого необходимо погружение на темную сторону процесса, изучение инструментов и механик взлома и пентеста. О этой очень интересной и полезной стороне вопроса почти никакой информации не приведено: хотя бы вводная информация, ссылки на литературу была бы достаточной, не говоря о том, что многие инструменты из этого набора очень активно используются и упрощают процессы и разработки и автоматического тестирования и сборки. Так совпало, что на данный момент я занимаюсь активным исследованием этой области и планировал делать какие-либо записки в блоге, во время процесса, и если есть у кого интерес- дайте знать, возможно, сделаю компиляцию информацию и оформлю статью тут. Но вообще, эта тема целой книги, которые периодически выходят.
                +2
                Назначение статьи — перечислить базовые пункты для разработчиков, особо не занимавшихся безопасностью, чтобы знали в какую сторону дальше копать. Мне и думаю многим другим было бы интересно почитать вашу более подробную статью.
                0
                Назначение статьи — перечислить базовые пункты для разработчиков, особо не занимавшихся безопасностью, чтобы знали в какую сторону дальше копать. Мне и думаю многим другим было бы интересно почитать вашу более подробную статью.
                  0
                  А все текстовые поля, в которых вводятся пароли, <...> не поддерживают возможность копирования/вставки
                  Про это на хабре есть статья, в которой рассказывается, почему это больше плохо, чем хорошо. Она, конечно, про веб, но для мобильных приложений, я думаю, эти доводы тоже справедливы

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