При разработке любого мобильного приложения, обрабатывающего пользовательские данные, важно уделить внимание безопасности. Особенно остро этот вопрос стоит для приложений, где фигурируют ФИО, номера телефонов, паспортов и другая личная информация. Наша компания разрабатывала и продолжает развивать нескольких проектов такого рода, в частности приложения для клиентов российских банков. На основе этого опыта мы выработали набор требований безопасности, которым руководствуемся. Естественно, каждый год появляются новые технологии и возможности, а вместе с ними — новые особенности поведения и уязвимости. В этой статье мы зафиксировали основные пункты обеспечения безопасности 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.
Комментариев нет:
Отправить комментарий