Привет, Хабр!
Меня зовут Юрий Шабалин, и, как я пишу в начале каждой своей статьи, мы разрабатываем платформу анализа защищенности мобильных приложений iOS и Android.
В этой статье мне бы хотелось затронуть тему безопасной конфигурации сетевого взаимодействия, а также немного расширить предыдущую статью по SSL Pinning для механизма защиты канала связи в iOS. А именно, я расскажу про App Transport Security: для чего он нужен, использовать ли его или отключать в приложениях, в чем его польза.
На эту тему существуют статьи на англоязычных ресурсах (здесь можно многие из них найти в разделе “Ссылки“), достаточно подробно эта тема раскрыта в документации Apple, но на русском я статей практически не встречал. Но даже если они есть, я с удовольствием добавлю свой материал с практическими примерами в эту небольшую копилку.
Оглавление
Введение
Apple от версии к версии iOS продолжает радовать нас различными нововведениями в части безопасности. В последнее время это больше связано с приватностью пользователей, их данными, местоположением и т.д. А для нас особый интерес представляет обновление механизма конфигурации безопасного сетевого соединения - App Transport Security - в iOS 9.0 в 2015 году.
Это событие можно рассматривать как попытку Apple сделать мобильные приложения более защищенными, точнее, дать разработчикам инструмент для самостоятельного управления настройками сетевого соединения, и не через код приложения, а посредством конфигурации специального домена в основном файле Info.plist
. Ключевыми задачами данного механизма являются отключение для приложения возможности общаться по незащищенному протоколу HTTP, а также обязательная поддержка последних версий TLS на сервере для обеспечения Perfect Forward Secrecy (про это можно подробно почитать в нашей статье про SSL Pinning). Изначально компания Apple собиралась сделать применение App Transport Security обязательным для всех приложений, представленных в App Store, начиная с января 2017 года. Однако, за несколько недель до этой даты решение изменили. И, хотя было анонсировано назначение новой даты, до сих пор использование ATS является необязательным.
Нечто подобное предложили в дальнейшем и в Android 7 в 2016 году, в виде файла настройки Network Security Config.
Что интересно, различные рекламные сервисы, например, Google AdMob, для своей корректной работы рекомендуют просто полностью отключить ATS в вашем приложении. Классный совет, спасибо, Google! Мы всегда знали, что ты - за безопасность!
Проверка сервера
Не всякий сервер подойдет для подключения с использованием ATS. Если ваш не удовлетворяет необходимым требованиям, то соединение завершится с ошибкойAn SSL error has occurred and a secure connection to the server cannot be made.
Вот каким должен быть сервер, чтобы применять ATS:
Все шифронаборы должны использовать алгоритмы, поддерживающие Perfect Forward Secrecy;
Наличие TLS версии не ниже 1.2;
Для всех сертификатов необходимо использовать, как минимум, отпечаток SHA256 с ключом RSA (2048 или выше) или с ключом 256 бит или более.
ATS-совместимые шифры
Для успешного подключения к серверу с использованием ATS на клиенте подойдут следующие шифронаборы:
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
Тестирование серверной части
Но даже если ваш сервер не удовлетворяет заданным требованиям безопасности для использования ATS, конфигурацию можно настроить. Хотя можно и задуматься о доработке серверной части.
Для того, чтобы определить, какие настройки необходимо проделать с ATS, чтобы он корректно работал, можно использовать команду nscurl
с параметром --ats-diagnostics
. Результатом вывода команды будут успешные и неуспешные проверки по всем пунктам ATS:
$ nscurl --ats-diagnostics https://stingray-mobile.ru/
Starting ATS Diagnostics
<...>
Default ATS Secure Connection
---
ATS Default Connection
Result : PASS
---
================================================================================
Allowing Arbitrary Loads
---
Allow All Loads
Result : PASS
---
================================================================================
Configuring TLS exceptions for stingray-mobile.ru
---
TLSv1.3
Result : PASS
---
---
TLSv1.2
Result : PASS
---
---
TLSv1.1
Result : PASS
---
---
TLSv1.0
Result : PASS
---
<...>
Если все проверки пройдены успешно, соединение может быть беспрепятственно установлено из приложения. А в случае каких-либо ошибок можно настроить соответствующее исключение в файле конфигурации.
К примеру, если не отработала проверка на Perfect Forward Secrecy:
$ nscurl --ats-diagnostics http://test.com
<...>
Default ATS Secure Connection
---
ATS Default Connection
2015-08-28 11:51:06.868 nscurl[7019:8960694] NSURLSession/
NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
Result : FAIL
---
<...>
Configuring PFS exceptions for test.com
---
Disabling Perfect Forward Secrecy
Result : PASS
---
<...>
В этом примере видно, что успешно установить соединение будет возможно, только если отключить Perfect Forward Secrecy. Поэтому в Info.plist
файле для домена test.com
необходимо указать NSExceptionRequiresForwardSecrecy=NO
.
Для того, чтобы проверить, какие шифронаборы используются и включены на вашем сервере, можно запустить nmap
со специальными параметрами
$ nmap --script ssl-enum-ciphers -p 443 www.cnn.com
Будет создан отчет, аналогичный приведенному ниже, в котором показано, какие версии TLS поддерживаются и какие шифронаборы можно применять.
$ nmap --script ssl-enum-ciphers -p 443 www.cnn.com
Starting Nmap 7.91 ( https://nmap.org ) at 2022-03-14 15:43 MSK
Nmap scan report for www.cnn.com (151.101.65.67)
Host is up (0.040s latency).
Other addresses for www.cnn.com (not scanned): 151.101.129.67 151.101.193.67 151.101.1.67
PORT STATE SERVICE
443/tcp open https
| ssl-enum-ciphers:
| TLSv1.0:
| ciphers:
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
| TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
| compressors:
| NULL
| cipher preference: server
| warnings:
| 64-bit block cipher 3DES vulnerable to SWEET32 attack
| TLSv1.1:
| ciphers:
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
| TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
| compressors:
| NULL
| cipher preference: server
| warnings:
| 64-bit block cipher 3DES vulnerable to SWEET32 attack
| TLSv1.2:
| ciphers:
| TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
| TLS_RSA_WITH_AES_128_GCM_SHA256 (rsa 2048) - A
| TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
| compressors:
| NULL
| cipher preference: server
| warnings:
| 64-bit block cipher 3DES vulnerable to SWEET32 attack
|_ least strength: C
Nmap done: 1 IP address (1 host up) scanned in 11.38 seconds
Кроме того, существуют сайты, например, http://ssllabs.com/ssltest/analyze.html, позволяющие проверить, совместимы ли общедоступные веб-сайты с ATS.
App Transport Security
Секция по настройке App Transport Security в Info.plist
называется NSAppTransportSecurity
и имеет следующую структуру:
NSAppTransportSecurity : Dictionary {
NSAllowsArbitraryLoads : Boolean
NSAllowsArbitraryLoadsForMedia : Boolean
NSAllowsArbitraryLoadsInWebContent : Boolean
NSAllowsLocalNetworking : Boolean
NSExceptionDomains : Dictionary {
<domain-name-string> : Dictionary {
NSIncludesSubdomains : Boolean
NSExceptionAllowsInsecureHTTPLoads : Boolean
NSExceptionMinimumTLSVersion : String
NSExceptionRequiresForwardSecrecy : Boolean
NSRequiresCertificateTransparency : Boolean
}
}
}
Далее мы разберем детально каждый из этих параметров: за что отвечает, какое значение имеет по умолчанию. Также поговорим о том, что может произойти, если эти параметры будут настроены небезопасным образом, и посмотрим на примерах, как автоматически это определять.
NSAllowsArbitraryLoads
Ключ NSAllowsArbitraryLoads
определяет состояние ATS в целом, видит включено или выключено оно для приложения. По умолчанию, для ключа NSAllowsArbitraryLoads
установлено значение NO
, то есть App Transport Security включен полностью. Установка ключа в значение YES
приведет к полному отключению всех проверок. Это означает, что система не будет запрещать приложению взаимодействовать с любыми доменами по протоколу HTTP, а также не будет применять никаких дополнительных проверок к установке сетевого соединения. Крайне не рекомендуется отключать ATS на уровне всего приложения. Лучше потратить немного времени и разобраться, что не работает и как это можно исправить.
Если вы все-таки решите отключить ATS, то рекомендую проконтролировать несколько моментов, которые автоматически проверяются при его использовании:
Шифры для сетевого взаимодействия приложения (и их надежность);
Протоколы, применяемые для отправки и получения данных (и их безопасность);
Наличие в приложении уязвимостей для перехода на более раннюю версию протокола шифрования;
Осуществление проверок приложения сертификатами, используемыми для TLS-подключений.
Как верно заметили в комментариях, поведение этого ключа зависит от используемой версии iOS:
iOS 9.0
ATS использует заданное значениеNSAllowsArbitraryLoads
(илиNO
по умолчанию) и игнорирует другие глобальные исключения.iOS 10.0 и более старшие версии
ATS игнорирует установленное значение NSAllowsArbitraryLoads, если какой-либо из следующих ключей присутствует в конфигурации:NSAllowsArbitraryLoadsForMedia
NSAllowsArbitraryLoadsInWebContent
NSAllowsLocalNetworking
NSAllowsLoadsForMedia
Это исключение относится к мультимедийному контенту, защищенному системой управления цифровыми правами (DRM) или шифрованием. По умолчанию, для ключа NSAllowsLoadsForMedia
установлено значение NO
. Если для него установлено значение YES
, ATS отключается для контента, отправляемого с использованием фреймворка AVFoundation. Обычно это происходит с приложениями, включающими в себя возможность работы с видео/аудио контентом.
Если по какой-то причине необходимо отключить ATS, рекомендуется дополнительно обратить внимание на то, чтобы мультимедийные данные, отправляемые приложением, не содержали конфиденциальных данных и были защищены с помощью DRM или шифрования.
По опыту: я не очень часто встречал подобные исключения и подобный контент в приложениях. Уверен, что они есть, но в моей практике они достаточно редки.
NSAllowsArbitraryLoadsInWebContent
Есть один интересный нюанс: если WebView (а именно, компоненты UIWebView
и WKWebView
) используется для отображения произвольных адресов, то невозможно составить весь их список и понять, правильно ли они настроены для соединения при помощи ATS. Apple предоставила два возможных решения данной проблемы.
Первое из них - это использование ключа NSAllowsArbitraryLoadsInWebContent
. Он определяет, возможно ли соединение по небезопасным протоколам из компонентов WebView. По умолчанию, для этого ключа установлено значение NO
. Если установить значение YES
, ATS будет отключен для WebView. Этот ключ появился только в десятой версии iOS. Есть несколько нюансов, относящихся к старым версиям iOS, но их мы рассматривать не будем, поскольку они уже неактуальны.
Второй, и более адекватный вариант, - использование SFSafariViewController
, который был специально разработан для этих целей. Обратимся к документации Apple:
“Используйте класс
SFSafariViewController
, если ваше приложение позволяет пользователям открывать произвольные Web-сайты. Используйте классWKWebView
для контента, который находится под вашим управлением”.
Применяя SFSafariViewController
, нужно помнить о некоторых специфических вещах:
ATS в данном компоненте отключен. Это позволяет загружать и отображать любой веб-контент с любых источников независимо от конфигурации HTTPS;
Cookies и данные Web-сайтов передаются Safari. Это позволяет, например, оставаться аутентифицированным на различных ресурсах, в которых пользователь логинился из браузера;
Способов контроля и управления по сравнению с
WKWebView
существенно меньше.
Из этих двух вариантов, при возможности, лучше использовать компонент SFSafariViewController
для отображения произвольных веб-сайтов вместо отключения ATS для всех WebView.
NSAllowsLocalNetworking
Ключ NSAllowsLocalNetworking
определяет работу ATS в локальной сети.
По умолчанию, для NSAllowsLocalNetworking
установлено значение NO
. Обычно это исключение используют приложения, подключающиеся к локальным устройствам для работы Интернета вещей (IoT). При отключении ATS убедитесь, что во время взаимодействия в локальной сети не передаются конфиденциальные данные, а также используется безопасное TLS-соединение.
NSExceptionDomains
При применении ключа NSExceptionDomains
появляется возможность настраивать исключения ATS для отдельных доменов. При этом не стоит забывать, что подразделы ATS внутри NSExceptionDomains
заменяют другие первичные ключи. Например, если приложение загружает мультимедиа из определенного домена, для которого используются и исключение NSAllowsLoadsForMedia
на верхнем уровне, и конфигурация NSExceptionDomains
, то параметры NSExceptionDomains
имеют приоритет. Другими словами, они заменяют значение ключа NSAllowsLoadsForMedia
верхнего уровня. Фактически, для конкретного домена силу имеют только настройки, указанные в исключениях для него. В разделе с примерами мы рассмотрим это чуть подробнее.
Вторая особенность заключается в том, что если домен в исключениях указан без какой-либо конфигурации, то он получит полную защиту ATS, даже если для параметра NSAllowsArbitraryLoads
установлено значение YES
. Таким образом, разработчик может отключить ATS глобально, но включить его для определенных доменов, указав их в ключе NSExceptionDomains
. Это еще один способ позволить приложению осуществлять загрузку данных с произвольных серверов без их проверки на совместимость с ATS (при включенном ATS для выбранных доменов).
Но, на самом деле, это достаточно плохая практика и, возможно, стоит еще раз посмотреть на архитектуру вашего приложения.
Если вы указываете исключения для доменов, ATS игнорирует любые ключи глобальной конфигурации, включая NSAllowsArbitraryLoads, для этого домена. Это работает, даже если вы оставите словарь для домена пустым и полностью доверитесь значениям его ключей по умолчанию.
NSIncludesSubdomains
Данный ключ определяет, будет ли применяться политика ATS для поддоменов.
По умолчанию, для ключа NSIncludesSubdomains
установлено значение NO
. Если установлено значение YES
, любая конфигурация ATS, включенная для определенного домена, будет использоваться для всех поддоменов. И, если установлен домен, но не настроено никаких дополнительных ключей, кроме NSIncludesSubdomains
, этот домен и его поддомены будут использовать ATS.
NSExceptionAllowsInsecureHTTPLoads
Ключ NSExceptionAllowsInsecureHTTPLoad
определяет, возможна ли передача незащищенного трафика HTTP на указанный домен.
По умолчанию, для этого ключа установлено значение NO
. Если установлено значение YES
, приложению будет разрешено отправлять HTTP-трафик на этот домен.
NSExceptionMinimumTLSVersion
Этот ключ позволяет снизить минимально допустимую версию TLS. По умолчанию, к таким версиям принадлежат TLS 1.2 и выше.
NSExceptionRequiresForwardSecrecy
Данный ключ определяет использование свойства Forward Secrecy для конкретного домена.
По умолчанию, для этого ключа установлено значение YES
. Если выставлено значение NO
, свойство Forward Secrecy будет отключено для этого домена.
NSRequiresCertificateTransparency
Данный ключ определяет использование свойства Certificate Transparency (прозрачность сертификатов) для конкретного домена.
По умолчанию, для этого ключа установлено значение NO
. Если для ключа установлено значение YES
, для сертификата домена потребуется метка времени Certificate Transparency.
Certificate Transparency - это проект Google, ориентированный на повышение безопасности системы выпуска сертификатов SSL. Если ваша организация или рассматриваемый домен поддерживает Certificate Transparency, рекомендуется включить данную опцию. Это помогает выявлять мошеннические центры сертификации и предотвращать атаки типа «человек посередине», уведомляя владельца, если его сертификат был скомпрометирован. Когда этот ключ включен, проверки сертификатов, связанные с Certificate Transparency, выполняются до установления соединения.
Certificate Pinning
Начиная с iOS 14, в AppTransport Security появился нативный механизм реализации прикрепления сертификатов (certificate pinning). В документации о нем сказано не много, основным источником информации является новость на портале. Резюмируем ее содержание:
В Info.plist можно указать набор сертификатов, которые App Transport Security (ATS) ожидает при подключении к указанным доменам
Закрепленный открытый ключ должен присутствовать либо в промежуточном, либо в корневом сертификате
Приложение не сможет подключиться к указанному в настройках домену, если проверка цепочки сертификатов окажется неуспешной
Можно связать несколько открытых ключей с одним доменным именем.
Для начала поговорим о том, как нам получить отпечаток, который нужно указать в настройках. Чтобы определить всю цепочку сертификатов (то, что будем закреплять), можно воспользоваться OpenSSL:
# servername is used to ensure SNI is requested
/usr/local/opt/openssl/bin/openssl s_client -showcerts -verify 5 -servername www.example.com -connect www.example.com:443 < /dev/null
# Output
verify depth is 5
CONNECTED(00000005)
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
verify return:1
depth=0 C = US, ST = California, L = Los Angeles, O = Internet Corporation for Assigned Names and Numbers, CN = www.example.org
verify return:1
---
Certificate chain
0 s:C = US, ST = California, L = Los Angeles, O = Internet Corporation for Assigned Names and Numbers, CN = www.example.org
i:C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
-----BEGIN CERTIFICATE-----
MIIG1TCCBb2gAwIBAgIQD74IsIVNBXOKsMzhya/uyTANBgkqhkiG9w0BAQsFADBP
<...>
vUzLnF7QYsJhvYtaYrZ2MLxGD+NFI8BkXw==
-----END CERTIFICATE-----
1 s:C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
i:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
-----BEGIN CERTIFICATE-----
MIIE6jCCA9KgAwIBAgIQCjUI1VwpKwF9+K1lwA/35DANBgkqhkiG9w0BAQsFADBh
<...>
8ks5T1KESaZMkE4f97Q=
-----END CERTIFICATE-----
2 s:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
i:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
<...>
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
---
Server certificate
subject=C = US, ST = California, L = Los Angeles, O = Internet Corporation for Assigned Names and Numbers, CN = www.example.org
issuer=C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 4654 bytes and written 747 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
DONE
Из вывода команды можно определить, что для http://www.example.com есть один конечный сертификат, сертификат промежуточного центра сертификации, а затем корневой сертификат.
Для получения отпечатка SPKI, можно сохранить любой сертификат в виде файла формата PEM, а затем передать его на вход в следующий скрипт (пример для MacOS):
#!/usr/bin/env bash
set -Eeuo pipefail
# Homebrew location of OpenSSL (built-in version is too out of date)
OPENSSL="/usr/local/opt/openssl/bin/openssl"
CERTHASH=$($OPENSSL x509 -inform pem -noout -outform pem -pubkey < "$1" | $OPENSSL pkey -pubin -inform pem -outform der | $OPENSSL dgst -sha256 -binary | $OPENSSL enc -base64)
echo -e "\nFingerprint for pinning: $CERTHASH"
Или же воспользоваться командой из документации:
$ cat ca.pem | openssl x509 -inform pem -noout -outform pem -pubkey | openssl pkey -pubin -inform pem -outform der | openssl dgst -sha256 -binary | openssl enc -base64
Результирующий отпечаток конечного сертификата в приведенном выше примере — mM294xslEgmvDODAxWWH2DeH4/bNgPBpgZvd7SfciuA=
, а центр сертификации — RQeZkB42znUfsDIIFWIRiYEcKl7nHwNFwWCrnMMJbVc=
.
Теперь мы можем добавить эти значения в Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>example.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSPinnedCAIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>RQeZkB42znUfsDIIFWIRiYEcKl7nHwNFwWCrnMMJbVc=</string>
</dict>
</array>
<key>NSPinnedLeafIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>mM294xslEgmvDODAxWWH2DeH4/bNgPBpgZvd7SfciuA=</string>
</dict>
</array>
</dict>
</dict>
</dict>
В приведенном выше примере мы указываем, что отпечаток открытого ключа связан с доменом example.org
и его поддоменами, например test.example.org
. Но вот поддомены третьего уровня и выше в эту проверку уже не попадают (например notinclude.test.example.org
). Также будем проверять центр сертификации, что указано в ключе NSPinnedCAIdentities
и сертификат конечного сервера, за который отвечает ключ NSPinnedLeafIdentities
.
Можно сразу же указать несколько отпечатков, что может быть полезно при ротации сертификатов на сервере:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>example.com</key>
<dict>
<key>NSPinnedLeafIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>i9HaIScvf6T/skE3/A7QOq5n5cTYs8UHNOEFCnkguSI=</string>
</dict>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>mM294xslEgmvDODAxWWH2DeH4/bNgPBpgZvd7SfciuA=</string>
</dict>
</array>
</dict>
</dict>
</dict>
Но как показывает практика, это работает не всегда. Такой способ хорош для URLSession
, но не подходит для API поверх CFNetwork
.
WKWebView
по-прежнему будет подключаться и загружать контент из домена, если открытый ключ SSL отличается от указанного вInfo.plist
SFSafariViewController
также не учитывает настройки вInfo.plist
. Такое поведение неудивительно, учитывая, что SFSafariViewController выполняется в отдельном процессе.
Ответ Apple пока неутешительный, поскольку не несет в себе никакой конкретики. Непонятно, будут ли когда-то настройки Certificate Pinning из AppTransport Security работать для всех API. Может быть, однажды это случится, ну а сейчас просто надо учитывать эту особенность при разработке.
Идеальная конфигурация
Если говорить про идеальную конфигурацию, то это, конечно, включение ATS в полном объеме на уровне всего приложения и без исключений для доменов:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsArbitraryLoadsForMedia</key>
<false/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<false/>
<key>NSPinnedDomains</key>
<dict>
<key>example.com</key>
<dict>
<key>NSPinnedLeafIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>i9HaIScvf6T/skE3/A7QOq5n5cTYs8UHNOEFCnkguSI=</string>
</dict>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>mM294xslEgmvDODAxWWH2DeH4/bNgPBpgZvd7SfciuA=</string>
</dict>
</array>
</dict>
</dict>
</dict>
К сожалению, такое встречается очень редко. Сегодняшние мобильные приложения зачастую используют большое количество сторонних сервисов, которые они не контролируют, и не могут гарантировать обеспечения должного уровня безопасности, например, использование корректной версии TLS.
Примеры
Отключение ATS
Полностью отключить ATS можно, указав флагNSAllowsArbitraryLoads=YES
. Такая конфигурация рекомендуется только для отладки.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
К сожалению, во многих приложениях мы до сих пор встречаем полностью отключенный ATS:
С чем это связано, до конца не ясно, но я надеюсь, что эта статья натолкнет на мысли о более детальной конфигурации приложения.
Работа с исключениями
Как было сказано выше, возможны ситуации, когда приложение взаимодействует с серверами, не отвечающими требованиям ATS. В этом случае нужно сообщить операционной системе, какие именно это домены, и указать их в Info.plist
вашего приложения (и отметить, какие именно требования не выполняются).
Здесь стоить помнить про одну важную деталь: конфигурация исключений может быть очень интересна злоумышленникам, особенно если вы оставляете в продуктивной сборке адреса тестовых стендов.
Как правило, такие стенды, если они имеют доступ в интернет, намного слабее “охраняются“ различными средствами защиты, мониторингом и прочими разработками. А значит, их интересно исследовать на предмет наличия уязвимостей. А дальше уже можно попробовать зайти в сеть и развернуться по-настоящему. Как я говорил в других статьях, одна уязвимость или даже недостаток в мобильном приложении не являются такой уж огромной проблемой (конечно, если это не какая-то инъекция или RCE). Однако, наличие нескольких слабостей помогает выстроить в вектор атаки, который сработает и принесет реальный ущерб приложению или пользователям.
Отключение ATS для всех соединений кроме одного
Не очень правильный, но все-таки имеющий право на жизнь способ - отключить ATS для всего, кроме ваших серверов. Конечно, не очень хорошо так делать, но это лучше, чем отключать ATS вообще. Вот как может выглядеть Info.plist
при такой конфигурации:
<?xml version=»1.0″ encoding=»UTF-8″?>
<!DOCTYPE plist PUBLIC «-//Apple//DTD PLIST 1.0//EN» «http://www.apple.com/DTDs/PropertyList-1.0.dtd»>
<plist version=»1.0″>
<dict>
…
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>api.test.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<false/>
</dict>
</dict>
</dict>
…
</dict>
</plist>
В этом случае приложение сможет осуществлять соединения со всеми серверами вне зависимости от корректности настройки HTTPS (да и просто по HTTP), но для выделенного в исключения домена api.test.com
подключения можно будет осуществлять только по HTTPS. Также для него будут применяться все остальные требования ATS.
Отключение PFS для всех поддоменов
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>test.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
</dict>
</dict>
При такой конфигурации подключение к домену test.com
и всех его поддоменов не будет требовать настройки Perfect Forward Secrecy. Она может применяться, например, если на вашем сервере не настроены правильные шифронаборы, позволяющие использовать это крайне полезное свойство.
Совмещение исключений
В следующем Info.plist
мы определим три исключения и затем разберем их более детально:
<?xml version=»1.0″ encoding=»UTF-8″?>
<!DOCTYPE plist PUBLIC «-//Apple//DTD PLIST 1.0//EN» «http://www.apple.com/DTDs/PropertyList-1.0.dtd»>
<plist version=»1.0″>
<dict>
…
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>api.insecuredomain.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<false/>
</dict>
<key>cdn.somedomain.com</key>
<dict>
<key>NSThirdPartyExceptionMinimumTLSVersion</key>
<string>1.1<string/>
</dict>
<key>otherdomain.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
</dict>
</dict>
…
</dict>
</plist>
api.insecuredomain.com
Первое исключение, которое мы определили, сообщает ATS, что связь с этим поддоменом отменяет требование обязательного использования HTTPS. Оно применяется только к конкретному адресу, указанному в исключении, и не затрагивает поддомены. Не стоит забывать, что ключ NSExceptionAllowsInsecureHTTPLoads
относится не только к использованию HTTPS, а и к ATS в целом. Таким образом, для этого домена отменяются все требования App Transport Security.
cdn.domain.com
Возможна ситуация, в которой приложение обращается к серверу, не использующему необходимую версию TLS (1.2 или выше). В этом случае можно определить исключение, указывающее минимальную версию TLS, которая может использоваться. Это будет лучше с точки зрения безопасности, чем полное отключение App Transport Security для этого домена.
otherdomain.com
Ключ NSIncludesSubdomains
сообщает App Transport Security, что исключение применяется к каждому поддомену указанного адреса. Кроме того, определяется, что домен может использовать шифры, которые не поддерживают Forward Secrecy (NSExceptionRequiresForwardSecrecy
).
Заключение
ATS - это очень хороший инструмент, позволяющий намного лучше и проще контролировать происходящее в вашем приложении. Видно, к каким серверам оно обращается, как они настроены и т.д. Кроме того, эта настройка помогает, в какой-то мере, контролировать состояние серверных частей приложения и поддерживать их безопасную конфигурацию. Да, механизм ATS - не только про клиента, он в какой-то степени заставляет backend придерживаться лучших практик безопасности. Так что перед тем, как просто отключить ATS в приложении, задумайтесь: “Может, пришло время правильно сконфигурировать серверную часть?” Также проанализируйте, какие ресурсы, кроме собственных, вы используете, и почему для них нужно отключать потенциальную защиту?
Конечно, хотелось бы видеть в настройке App Transport Security и другие параметры, например, связанные с SSL Pinning (по аналогии с тем, как это сделано в Android). Это позволило бы не искать свои пути для каждого фреймворка, а задавать это на уровне системы. Но, к сожалению, думаю, что этого мы уже не дождемся.
Надеюсь, что эта статья поможет всем интересующимся понять нюансы настройки App Transport Security, а возможно и заставит правильно его сконфигурировать для своего приложения, вместо полного отключения.