Статьи

Безопасное кодирование в Swift 4

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

Это руководство представляет собой руководство по безопасному кодированию, в котором будут рассмотрены изменения в Swift 4, а также новые опции инструментов, доступные в Xcode 9, которые помогут вам смягчить уязвимости безопасности.

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

Swift по большей части избавляется от указателей, но все же позволяет вам взаимодействовать с C. Многие API, включая весь базовый API Apple, полностью основаны на C, поэтому очень просто ввести использование указателей в Swift.

К счастью, Apple правильно UnsafePointer<T> типы указателей: UnsafePointer<T> , UnsafeRawPointer<T> , UnsafeBufferPointer<T> и UnsafeRawBufferPointer . Придет время, когда API, с которым вы взаимодействуете, вернет эти типы, и основное правило при их использовании — не хранить и не возвращать указатели для дальнейшего использования . Например:

1
2
3
4
5
6
7
let myString = «Hello World!»
var unsafePointer : UnsafePointer<CChar>?
myString.withCString { myStringPointer in
    unsafePointer = myStringPointer
}
//sometime later…
print(unsafePointer?.pointee)

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

Указатели на строки и массивы также не имеют проверки границ. Это означает, что легко использовать небезопасный указатель на массив, но случайно получить доступ за его пределами — переполнение буфера .

01
02
03
04
05
06
07
08
09
10
11
var numbers = [1, 2, 3, 4, 5]
numbers.withUnsafeMutableBufferPointer { buffer in
             
    //ok
    buffer[0] = 5
    print(buffer[0])
                 
    //bad
    buffer[5] = 0
    print(buffer[5])
}

Хорошая новость заключается в том, что Swift 4 пытается завершить работу приложения, а не продолжать то, что можно назвать неопределенным поведением . Мы не знаем, на какой buffer[5] указывает! Однако Свифт не поймает каждый случай. Установите точку останова после следующего кода и посмотрите на переменные a и c . Они будут установлены на 999 .

01
02
03
04
05
06
07
08
09
10
func getAddress(pointer:UnsafeMutablePointer<Int>) -> UnsafeMutablePointer<Int>
{
    return pointer
}
var a = 111
var b = 222
var c = 333
let pointer : UnsafeMutablePointer<Int> = getAddress(pointer: &b)
pointer.successor().initialize(to: 999)
pointer.predecessor().initialize(to: 999)

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

В следующем примере мы делаем распределение с емкостью только одного Int8 . Выделения хранятся в куче, поэтому следующая строка переполнит кучу . Для этого примера Xcode только предупреждает вас с записью в консоли, которая gets небезопасно.

1
2
let buffer = UnsafeMutablePointer<Int8>.allocate(capacity:1)
gets(buffer)

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

Вы можете подумать, что довольно трудно запомнить и найти все разные случаи. Таким образом, чтобы помочь вам, Xcode поставляется с очень полезным инструментом под названием Address Sanitizer.

Address Sanitizer был улучшен в Xcode 9. Это инструмент, который помогает вам обнаружить недопустимый доступ к памяти, такой как примеры, которые мы только что видели. Если вы будете работать с типами Unsafe* , рекомендуется использовать инструмент Address Sanitizer. По умолчанию он не включен, поэтому для его включения перейдите в « Продукт»> «Схема»> «Редактировать схему»> «Диагностика» и проверьте « Средство очистки адресов» . В Xcode 9 есть новая подопция, Определить использование стека после возврата . Этот новый параметр обнаруживает уязвимости использования после объема и использования после возврата из нашего первого примера.

Иногда упускается из виду целочисленное переполнение . Это связано с тем, что целочисленные переполнения являются дырами в безопасности только при использовании в качестве индекса или размера буфера или если неожиданное значение переполнения изменяет поток критического кода безопасности. Swift 4 улавливает наиболее очевидные целочисленные переполнения во время компиляции, например, когда число явно больше, чем максимальное значение целого числа.

Например, следующее не будет компилироваться.

1
2
var someInteger : CInt = 2147483647
someInteger += 1

Но во многих случаях число будет поступать динамически во время выполнения, например, когда пользователь вводит информацию в UITextField . Undefined Behavior Sanitizer — новый инструмент в Xcode 9, который обнаруживает целочисленное переполнение со знаком и другие ошибки несоответствия типов. Чтобы включить его, перейдите в Product> Scheme> Edit Scheme> Diagnostics и включите Undefined Behavior Sanitizer . Затем в « Настройках сборки»> «Неопределенный очиститель поведения» установите для параметра « Включить дополнительные проверки целых чисел» значение Да .

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

01
02
03
04
05
06
07
08
09
10
11
var numbers = [1, 2, 3]
for number in numbers
{
    print(number)
    numbers = [4, 5, 6] //<- accident ???
}
         
for number in numbers
{
    print(number)
}

Здесь мы просто заставили массив numbers указывать на новый массив внутри цикла. Тогда на что указывает number ? Обычно это называется висячей ссылкой, но в этом случае Swift неявно создает ссылку на копию буфера вашего массива на время цикла. Это означает, что оператор print будет фактически выводить 1, 2 и 3 вместо 1, 4, 5 …. Это хорошо! Swift спасает вас от неопределенного поведения или сбоя приложения, хотя вы, возможно, и не ожидали такого вывода. Ваши равноправные разработчики не ожидают, что ваша коллекция будет видоизменяться во время перечисления, поэтому, в целом, будьте особенно внимательны при перечислении, чтобы не изменять коллекцию.

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

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

Атаки форматной строки происходят, когда входная строка анализируется в вашем приложении как команда, которую вы не намеревались. Хотя чистые строки Swift не восприимчивы к атакам форматных строк, классы Objective-C NSString и Core Foundation CFString есть, и они доступны из Swift. Оба этих класса имеют такие методы, как stringWithFormat .

Допустим, пользователь может ввести произвольный текст из UITextField .

1
let inputString = «String from a textfield %@ %d %p %ld %@ %@» as NSString

Это может быть дырой в безопасности, если строка формата обрабатывается напрямую.

1
2
let textFieldString = NSString.init(format: inputString) //bad
let textFieldString = NSString.init(format: «%@», inputString) //good

Swift 4 пытается обработать отсутствующие аргументы строки формата, возвращая 0 или NULL, но особенно важно, если строка будет возвращена во время выполнения Objective C.

1
2
NSLog(textFieldString);
NSLog(«%@», textFieldString);

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

Другим крупным виновником является NSPredicate , который может принимать строку формата, которая используется для указания того, какие данные извлекаются из Core Data. Такие пункты, как LIKE и CONTAINS разрешают использование подстановочных знаков, и их следует избегать или, по крайней мере, использовать только для поиска. Идея состоит в том, чтобы избежать перечисления учетных записей, например, когда злоумышленник вводит «a *» в качестве имени учетной записи. Если вы измените предложение LIKE на == , это означает, что строка буквально должна соответствовать «a *».

Другие распространенные атаки происходят, если досрочно завершить ввод строки символом одинарных кавычек, чтобы можно было вводить дополнительные команды. Например, вход в систему можно обойти, введя ') OR 1=1 OR (password LIKE '* в UITextField . Эта строка переводится как «где пароль похож на что угодно», что полностью обходит аутентификацию. Решение состоит в том, чтобы полностью экранировать любые попытки внедрения путем добавления ваших собственных двойных кавычек в коде. Таким образом, любые дополнительные кавычки от пользователя рассматриваются как часть входной строки, а не как специальный завершающий символ:

1
let query = NSPredicate.init(format: «password == \»%@\»», name)

Еще один способ обезопасить себя от этих атак — просто найти и исключить определенные символы, которые, как вы знаете, могут быть вредными в строке. Примерами могут быть кавычки или даже точки и косые черты. Например, можно выполнить атаку обхода каталога, когда входные данные передаются непосредственно в класс FileManager . В этом примере пользователь вводит «../» для просмотра родительского каталога пути вместо предполагаемого подкаталога.

01
02
03
04
05
06
07
08
09
10
11
let userControllerString = «../» as NSString
let sourcePath = NSString.init(format: «%@/%@», Bundle.main.resourcePath! , userControllerString)
NSLog(«%@», sourcePath)
         
//Instead of Build/Products/Debug/Swift4.app/Contents/Resources, it will be Build/Products/Debug/Swift4.app/Contents
let filemanager:FileManager = FileManager()
let files = filemanager.enumerator(atPath: sourcePath as String)
while let file = files?.nextObject()
{
    print(file)
}

Другие специальные символы могут включать завершающий байт NULL, если строка используется как строка C. Указатели на строки C требуют завершающего байта NULL. Из-за этого можно манипулировать строкой, просто вводя нулевой байт. Злоумышленник может захотеть завершить строку раньше, если был установлен флаг, например needs_auth=1 , или когда доступ включен по умолчанию и явно отключен, например при is_subscriber=0 .

1
2
3
4
let userInputString = «username=Ralph\0» as NSString
let commandString = NSString.init(format: «subscribe_user:%@&needs_authorization=1», userInputString)
NSLog(«%s», commandString.utf8String!)
// prints subscribe_user:username=Ralph instead of subscribe_user:username=Ralph&needs_authorization=1

Парсинг строк HTML, XML и JSON также требует особого внимания. Самый безопасный способ работы с ними — использовать собственные библиотеки Foundation, которые предоставляют объекты для каждого узла, такие как класс NSXMLParser . Swift 4 представляет безопасную сериализацию для внешних форматов, таких как JSON. Но если вы читаете XML или HTML, используя собственную систему, убедитесь, что специальные символы из пользовательского ввода не могут быть использованы для инструктирования интерпретатора.

  • < должен стать &lt .
  • > должно быть заменено на &gt .
  • & должно стать &amp .
  • Внутри значений атрибутов любое или ' должно стать « &quot и « &apos соответственно.

Вот пример быстрого способа удалить или заменить определенные символы:

1
2
var myString = «string to sanitize;»
myString = myString.replacingOccurrences(of: «;», with: «»)

Последняя область для инъекционных атак — внутри обработчиков URL. Убедитесь, что пользовательский ввод не используется непосредственно внутри пользовательских URL-обработчиков openURL и didReceiveRemoteNotification . Убедитесь, что URL-это то, что вы ожидаете, и что он не позволяет пользователю произвольно вводить информацию, чтобы манипулировать вашей логикой. Например, вместо того, чтобы позволить пользователю выбирать, к какому экрану в стеке перемещаться по индексу, разрешите только определенные экраны, использующие непрозрачный идентификатор, например t=es84jg5urw .

Если вы используете в своем приложении WKWebView , было бы неплохо проверить URL-адреса, которые также будут там загружены. Вы можете переопределить decidePolicyFor navigationAction , которая позволяет выбрать, хотите ли вы продолжить запрос URL-адреса.

Некоторые известные приемы веб-просмотра включают загрузку пользовательских схем URL, которые разработчик не намеревался, например, app-id: запустить совершенно другое приложение или sms: отправить текст. Обратите внимание, что во встроенных веб-просмотрах не отображается панель с URL-адресом или статусом SSL (значок замка), поэтому пользователь не может определить, является ли соединение надежным.

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

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

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

Так же, как Swift становится более уязвимым, когда вы взаимодействуете с C, взаимодействие с Objective-C вносит отдельные уязвимости в таблицу.

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

Наиболее распространенная уязвимость заключается в динамическом переключении важного метода безопасности на другой метод. Например, метод, который возвращает, если пользователь подтвержден, может быть заменен другим методом, который почти всегда будет возвращать true, таким как isRetinaDisplay . Минимизация использования Objective-C сделает ваше приложение более устойчивым к атакам такого типа.

В Swift 4 методы в классах, которые наследуются от класса Objective C, доступны только для среды выполнения Objective C, если эти методы или сами классы помечены @attribute . Часто вместо этого вызывается функция Swift, даже если @objc атрибут @objc . Это может произойти, когда метод имеет атрибут @objc но никогда не вызывается из Objective-C.

Другими словами, Swift 4 вводит меньше @objc , поэтому это ограничивает поверхность атаки по сравнению с предыдущими версиями. Тем не менее, для поддержки функций времени исполнения, двоичные файлы на основе Objective-C должны хранить много информации о классе, которую нельзя удалить. Этого достаточно для того, чтобы реверс-инженеры перестроили интерфейс класса, чтобы выяснить, например, какие разделы безопасности нужно исправить.

В Swift меньше информации, представленной в двоичном файле, и имена функций искажены. Однако искажение может быть отменено инструментом Xcode swift-demangle. Фактически, функции Swift имеют согласованную схему именования , указывающую, является ли каждая из них функцией Swift или нет, частью класса, именем и длиной модуля, именем и длиной класса, именем и длиной метода, атрибутами, параметрами и типом возвращаемого значения.

Эти имена в Swift 4 короче. Если вас интересует обратный инжиниринг, убедитесь, что в версии выпуска вашего приложения символы удалены, перейдя в « Параметры сборки»> «Развертывание»> «Обрезать символы Swift» и установите для параметра значение « Да».

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

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

1
2
3
4
@inline(__always) func myFunction()
{
    //…
}

Размышления о безопасности должны быть большой частью развития. Просто ожидание того, что язык будет безопасным, может привести к уязвимостям, которых можно было бы избежать. Swift популярен для разработки под iOS, но он доступен для настольных приложений macOS, tvOS, watchOS и Linux (так что вы можете использовать его для серверных компонентов, где вероятность использования кода намного выше). Песочница в приложении может быть нарушена, например, в случае взломанных устройств, которые позволяют запускать неподписанный код, поэтому важно все же думать о безопасности и обращать внимание на уведомления Xcode во время отладки.

Последний совет — обрабатывать предупреждения компилятора как ошибки. Вы можете заставить XCode сделать это, перейдя в Настройки сборки и установив Лечить Предупреждения как Ошибки в Да . Не забывайте модернизировать настройки вашего проекта при переходе на Xcode 9, чтобы получать улучшенные предупреждения, и, наконец, что не менее важно, использовать новые функции, доступные, приняв Swift 4 сегодня!

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

Для ознакомления с другими аспектами безопасного кодирования для iOS ознакомьтесь с некоторыми другими моими публикациями здесь на Envato Tuts +!

  • iOS SDK
    Защита данных iOS в состоянии покоя: защита данных пользователя
    Коллин Штюрт
  • iOS SDK
    Защита связи на iOS
    Коллин Штюрт
  • Безопасность
    Создание цифровых подписей с помощью Swift
    Коллин Штюрт