Примеры уязвимостей
Примеры уязвимостей в Swift
Client Side Injection
Все данные, передаваемые от пользователя, должны проходить проверку на соответствие ожидаемому формату, при их обработке управляющие конструкции необходимо экранировать.
Ниже показан пример встраивания HTML-кода через форматную строку и его отображение в компоненте WKWebView.
let view = WKWebView()
let injUserData = "<strong>123</strong>"
view.loadHTMLString("Test \(injUserData) string", baseURL: nil)
// view.load(request)
return view
В общем случае, инъекция может произойти подобным образом не только в HTML-страницы, но и в запросы к серверу в JSON- , XML- структуры, в любые другие форматы хранения и обработки информации. А также не только из полей ввода в UI, но такие данные могут прийти при обработке Universal Links, открытии файлов и изображений пользователя, через механизм Share и тд.
Правильное использование биометрии
В iOS для локальной аутентификаци (Passcode, TouchID, FaceID) на устройстве используется Local Authentication Framework
и Security.framework
.
Рассмотрим аутентификацию на примере с TouchID. Разработчики могут ее задействовать с помощью функции evaluatePolicy
класса LAContext
. Функция evaluatePolicy
возвращает булевое значение, определяющее прошел ли пользователь аутентификацию или нет.
let context = LAContext()
var error: NSError?
guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
// Could not evaluate policy; look at error and present an appropriate message to user
}
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "Please, pass authorization to enter this area") { success, evaluationError in
guard success else {
// User did not authenticate successfully, look at evaluationError and take appropriate action
}
// User authenticated successfully, take appropriate action
}
Проверка, опирающаяся только на результат результат булевой функции и не предполагающая какую-то последующую обработку данных (как в примере выше), может быть обойдена в некоторых случаях атакующим путем использования патчей или инструментации (внедрение в уже запущенный процесс своего кода).
Корректнее использовать для локальной аутентификации на устройстве сервис Keychain. При создании записи в Keychain можно с помощью атрибута SecAccessControl указать например, что значение записи можно получить только если на устройстве включен вход по Passcode (kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
) и доступ разрешен после прохождения TouchID (SecAccessControlCreateFlags.biometryCurrentSet
):
guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, SecAccessControlCreateFlags.biometryCurrentSet, &error) else {
// ошибка
return
}
var query: [String: Any] = [:]
query[kSecClass as String] = kSecClassGenericPassword
...
query[kSecAttrAccessControl as String] = accessControl
let status = SecItemAdd(query as CFDictionary, nil)
Затем, при обращении к этой записи, у пользователя будет запрошена аутентификация. При успешном ее прохождении будет возвращено значение записи из Keychain, которое можно будет использовать, например, для расшифрования сессии и пользователя и продолжения работы приложения, в целом.
var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
if status == noErr {
let someSecretValue = String(data: queryResult as! Data, encoding: .utf8)!
// используем someSecretValue
} else {
// авторизация не выполнена
}
Про Access Control Flags в работе с записями в Keychain
Как следует из примера выше, с точки зрения безопасности лучше использовать метод локальной аутентификации на основе Keychain сервиса. Вам следует:
Проверить, что все процессы, обрабатывающие важную информацию (например, подтверждение платежных транзаций пользователя), защищены этим методом
Проверить, что флаги управления доступом установлены (они гарантируют, что записи в Keychain могут быть разблокированы только путем аутентификации пользователя). Это можно сделать с помощью одного из следующих флагов:
kSecAccessControlBiometryCurrentSet
— требуется вход по TouchID или FaceID. При добавлении нового отпечатка доступ к записи получить не удасться.kSecAccessControlBiometryAny
— требуется вход по TouchID или FaceID. Доступ к полю сохраняется при добавлении новых отпечатков. Этот метод слабееkSecAccessControlBiometryCurrentSet
. С точки зрения безопасности, при добавлении новых отпечатков, лучше уведомить об этом пользователя и заставить его повторно авторизоваться в вашем приложении.kSecAccessControlUserPresence
— вход по Passcode, если вход по TouchID или FaceID невозможен. Этот метод слабееkSecAccessControlBiometryAny
, и с точки зрения безопасности не рекомендуется (так как код легче получить, нежели пройти биометрию). Однако, с точки зрения удобства для пользователя он предпочтителен.
Проверить, что при создании записей в Keychain, для которых предполагается доступ по биометрии, был указан один из флагов —
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
илиkSecAttrAccessibleWhenPasscodeSet
Sensitive Data In Memory
Часто возникает необходимость обрабатывать и передавать конфиденциальную и иную (пароли, токены, куки..) информацию через память процесса. Рекомендуется, чтобы ваше приложение создавало как можно меньше копий этих данных в течение как можно меньшего времени, а по окончании работы удаляло их из памяти (например, обнуляя значения, затирая нулями и тп). Другими словами, вам нужна централизованная обработка конфиденциальных данных на основе примитивных и изменяемых структур данных.
В языке Swift не так много типов, которые предоставляют прямой доступ к памяти. К таким типам относятся простые типы — char, int и тп, а так же коллекции — Array, Set и Dictionary — над этими простыми типами.
Избегайте типов данных Swift, отличных от коллекций, независимо от того, считаются ли они изменяемыми. Многие типы данных Swift хранят свои данные по значению, а не по ссылке. Хотя это позволяет изменять память, выделенную для простых типов, таких как char и int, обработка сложного типа, такого как String по значению, включает в себя скрытый слой объектов, структур или примитивных массивов, память которых не может быть напрямую доступна или изменена.
Например, многие думают, что следующее приводит к изменяемой строке в Swift, но на самом деле это пример переменной, сложное значение которой может быть скопировано, а не изменено:
var str1 = "Goodbye" // "Goodbye", base address: 0x0001039e8dd0
str1.append(" ") // "Goodbye ", base address: 0x608000064ae0
str1.append("cruel world!") // "Goodbye cruel world", base address: 0x6080000338a0
str1.removeAll() // "", base address 0x00010bd66180
Обратите внимание, что базовый адрес базового значения изменяется с каждой строковой операцией. Вот проблема: чтобы безопасно стереть конфиденциальную информацию из памяти, мы не хотим просто изменять значение переменной; мы хотим изменить фактическое содержимое памяти, выделенной для текущего значения. Swift не предлагает такой функции.
Last updated
Was this helpful?