Безопасность мобильного приложения в iOS!

Банковских МП с каждый годом становится больше, люди уже привыкли управлять своими средствами одним касанием. Этим пользуются злоумышленники.

Я хочу рассмотреть основные уязвимости МП и пути их решения, дать рекомендации к защите данных ваших пользователей.

1. Защита канала передачи данных

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

Одним из способов уменьшения возможности такой атаки является использование HTTPS: расширение протокола HTTP, поддерживающее шифрование по протоколам SSL и TLS.

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

По умолчанию при инициации SSL-соединения клиент проверяет, что сертификат сервера:

  • имеет проверяемую цепочку сертификатов до достоверного (корневого) сертификата.
  • соответствует имени хоста.

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

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

Про реализацию пиннинга с помощью Alamofire можно посмотреть на этом сайте.

Alamofire поддерживает der сертификаты. Обычно сертификаты поставляются заказчиком в pem - формате. Подробнее про форматы и работы с ними можно прочитать тут

2. Механизм сессии

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

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

3. Jailbreak

Пользователи МП иногда ставят под угрозу свои устройства, устанавливая Jailbreak на iOS- устройстве. Jailbreak позволяет получить полный доступ к файловой системе.

iOS не зря считается одной из самых безопасных операционных систем. Безопасность iOS достигается за счёт закрытости: программы могут ставиться лишь из одного источника (магазина App Store), установленным приложениям разрешено работать только с файлами в собственных папках, а пользователь не имеет возможности отредактировать какие-либо важные системные файлы.

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

Данная проблема решается проверкой на взломанность системы

extension SecurityHelper {

  class var isJailbroken: Bool {
      return hasCydiaObBoard() || hasCidyaBundle() || hasSignerIdentity() || outSandbox()
  }

  private class func hasCydiaObBoard() -> Bool {
      let url = URL(string: "cydia://package/com.example.package")
      return UIApplication.shared.canOpenURL(url!)
  }

  private class func hasCidyaBundle() -> Bool {
      let filePath = "/Applications/Cydia.app"
      return FileManager.default.fileExists(atPath: filePath)
  }

  private class func hasSignerIdentity() -> Bool {
      return Bundle.main.infoDictionary?["SignerIdentity"] != nil
  }

  private class func outSandbox() -> Bool {
      let filePath = "/private/var/lib/apt/"
      return FileManager.default.fileExists(atPath: filePath)
  }
}

4. Хранение КВД (критически важных данных) в коде

Например, хранения КВД внутри кода: в статических константных строках, в ресурсах приложения и т.п.

Одним из примеров является хранение соли для пароля (password salt) в константе, которая применяется по всему коду для шифрования паролей; хранение приватного ключа для асимметричных алгоритмов; хранение паролей и логинов для серверных узлов или баз данных.

Это касается и функций, генерируемых каждый раз один и тот же ключ.

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

5. Передача КВД во внешнюю среду в открытом виде

Относится к передаче КВД без использования шифрования по любому доступному каналу связи с внешней средой.

Следует шифровать КВД перед отправкой на сервер.

6. Логирование

Следует отключать логирование (вывод отладочной информации) в релизных сборках, так как данные могут попасть злоумышленникам, а ведь очень часто там могут присутствовать КВД.

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

Я думаю, что ответ на вопрос “Зачем?” тут расписывать бесполезно, так как мы и так все понимаем.

Самый простой вариант - разлогинивать пользователя при каждом входе в приложения. (Необходимо вводить логин и пароль при каждом запуске приложения)

Многие для удобства пользователей вводят некоторые “пользовательские” пароли. Это осуществляется через ввод некоторого PIN-кода, графического ключа, сканированием отпечатка пальца или лица и тд.

Важным условием является то, что пользовательский пароль должен иметь ограниченное количество попыток ввода, и , в случае неудачи, МП должно разлогиниваться.

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

8. Использование “шторки”

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

Обычно для этого обрабатываются в AppDelegate 2-е функции:

applicationDidEnterBackground - для показа шторки

applicationDidBecomeActive - для скрытия шторки

private let securityView = UIView() // ваша шторка

extension UIWindow {    

    func setContentHidden(_ hidden: Bool) {
        if hidden {
            // перекрываем экран приложения шторкой
            securityView.frame = frame
            addSubview(securityView)
        } else {
            // убираем шторку
            securityView.removeFromSuperview()
        }
    }

}

9. Требования к UI

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

Это поможет исключить возможность чтения этих данных с экрана устройства окружающими Вас людьми (потенциальных нарушителей безопасности)

10. Библиотеки

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

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

11. Хранение КВД в незащищенных хранилищах

Очень многие разработчики хранят данные от учетных записей в незашифрованном виде в БД или NSUserDefaults. Это НЕДОПУСТИМО.

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

Простой пример iExplorer - утилита, созданная для просмотра и работы с файлами, которые хранятся в памяти iOS устройств. Можно просмотреть ваши файлы даже без Jailbreak. Данные, которые хранятся в NSUserDefaults представляют их себя обычные файлы с расширением *.plist.

А данные, которые хранятся в бд, например Core Data, представляет чаще всего файлы *.sqlite, который тоже можно считать.

Для хранение КВД в iOS есть Keychain, который предоставляет максимальную защиту ваших данных, кроме случаев когда устройства с Jailbreak. Если вы не осуществляете проверки на Jailbreak, то перед сохранением данных в Keychain необходимо их шифровать.

В Keychain есть несколько вариантов защиты :

  • When Passcode Set
  • When Unlocked
  • After First Unlock
  • Always

Это указывает на то, когда вы можете работать с данными из Keychain. Используйте наиболее подходящий под ваши нужды. Почти все из этих вариантов дублируются с приставкой ThisDeviceOnly. Это указывает на то, что при копировании(импорта) Keychain, сохраненные данные не будут восстановлены на другое устройство. (Например с помощью iTunes BackUp)

Мы используем KeychainAccess для работы с Keychain.

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

Обычно перед хранением их хэшируют c применением salt (соль).

salt - это строка, которая хэшируются вместе с квд и призвана усложнить взлом salt может хранится на сервере, может хранится как константа в приложении (не рекомендуется)

Лучшим решением будет генерировать соль при необходимости и сохранять ее в Keychain для следующего использования.

by Eugene Lezov

Используемые материалы:

и многое другое.

Written on July 24, 2018