Первоначально разработанный для браузеров, SSL/TLS-протокол позже стал стандартом де-факто вообще для всех защищенных интернет-коммуникаций. Сейчас он используется для удаленного администрирования виртуальной инфра­структуры, развернутой в облаке, для передачи платежных реквизитов покупателя от серверов электронной коммерции к платежным процессорам, таким как PayPal и Amazon, для пересылки локальных данных в облачное хранилище, сохранения переписки в мессенджерах и аутентификации серверов в мобильных приложениях iOS и Android.


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



SSL/TLS-протокол: хотели как лучше…


В теории, защищённое SSL/TLS-протоколом соединение должно обеспечивать конфиденциальность, достоверность и целостность коммуникаций клиентского и серверного софта, – даже в условиях присутствия активного продвинутого злоумышленника из сети: когда сеть полностью захвачена врагом, DNS отравлен, а точки доступа и маршрутизаторы, коммутаторы и WiFi контролируются злоумышленником; злоумышленником, который помимо всего прочего контролирует SSL/TLS-бэкэнд. Кроме того, когда клиентский софт пытается подключиться к законному серверу, – злоумышленник может подменить сетевой адрес сервера (например, через отравление DNS), и вместо законного сервера, перенаправить клиента на свой злонамеренный сервер.


Безопасность коммуникаций в таких суровых условиях, как известно, целиком зависит от адекватности проверки криптографического сертификата, предоставленного сервером при установке соединения. В том числе от адекватности реализации набора шифров (ciphersuite), которыми клиент и сервер пользуются при обмене данными. Для того чтобы SSL/TLS-соединение было полностью безопасным, клиентский софт, в числе прочего, должен тщательно удостовериться, что:


  • сертификат выдан действующим органом сертификации;
  • срок его действия не истёк (или сертификат не был отозван);
  • в списке перечисленных в сертификате имён присутствует тот домен, к которому производится подключение.

…а получилось как всегда: примеры провальной проверки SSL/TLS-сертификата


Однако во многих приложениях и библиотеках, для которых безопасность коммуникаций очень критична, процедура проверки SSL/TLS-сертификата, – и даже EV-SSL, сертификата с расширенной проверкой [4], – полностью провальная. Во всех популярных операционных системах: Linux, Windows, Android и iOS. Среди уязвимого софта, библиотек и middleware-сервисов можно выделить следующие [1]:


  • Amazon’овская Java-библиотека EC2 и все облачные фронтэнд-клиенты построенные на её основе.
  • Amazon’овский и PayPal’овский торговый SDK, ответственный за доставку платёжных реквизитов от сайтов (на которых развёрнута инфраструктура онлайн-коммерции) к платёжным шлюзам.
  • Интегрированные «корзины», такие как osCommerce, ZenCart, Ubercart и PrestaShop, которые не проверяют сертификаты вообще.
  • AdMob-код используемый мобильным софтом для показа контекстной рекламы.
  • Интерфейсные фронтэнд-компоненты ElephantDrive и FilesAnywhere, ответственные за взаимодействие с облачным хранилищем.
  • Android’ная библиотека Pusher и весь софт, который использует Pusher API для управления обменом мгновенными сообщениями (например, GitHub’овский Gaug.es).
  • Apache HttpClient (версия 3.x); Apache Libcloud; и все клиентские подключения к серверам Apache ActiveMQ и т.п.
  • SOAP middleware-сервисы Java, в том числе Apache Axis, Axis 2, Codehaus XFire; а также весь софт, который на базе этих middleware-сервисов построен.
  • API-инструменты Elastic Load Balancing.
  • Weberknecht-реализация WebSockets’ов.
  • А также весь мобильный софт, построенный на базе перечисленных выше библиотек и middleware-сервисов (чтобы понять, что такое middleware-сервисы, смотри рис. 1); в том числе iOS-клиент хостинг-провайдера Rackspace.

Рисунок 1. Что такое middleware-сервисы


Кроме того, в [2] перечислено ещё больше сотни уязвимых мобильных приложений (см. рис. 2). В том числе: Android's Google Cloud Messaging, Angie's List Business Center Passwords, AT&T Global Network Client, CapitalOne Spark Pay, Cisco OnPlus (remote access), Cisco Technical Support, Cisco Webex, Cisco WebEx Passwords, Dominos Pizza, E-Trade, Freelancer, Google Earth, Huntington Mobile (Bank), Intuit Tax Online Accountant, iTunes Connect, Microsoft Skype, Oracle Now, Pinterest, SafeNet (VPN client), SouthWest Airlines, Uber, US Bank – Access Online, WesternUnion, WordPress, Yahoo! Finance, Yahoo! Mail.


Рисунок 2. Небольшая выборка из списка уязвимых мобильных приложений


Логические уязвимости SSL/TLS-протокола


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


Поставщики мобильного софта, которые берут за основу сэмпл-код AdMob для связи своих приложений с AdMob-аккаунтом, тоже уязвимы, – они позволяют атакующему захватывать учётные данные, и получать доступ ко всем его Google-сервисам. К примеру, из-за некорректной проверки сертификатов в таких мессенджерах как Trillian и AIM, MiTM-злоумышленник может похитить учётные данные для входа ко всем сервисам Google (включая Gmail), Yahoo!; и также к сервисам Windows Live (в т.ч. SkyDrive). Среди других уязвимостей, которыми страдает современный небраузерный веб-софт: использование неправильных регулярных выражений при сравнении имени хоста; игнорирование результатов проверки корректности сертификата; случайное или преднамеренное отключение проверки. [1]


Другие распространённые уязвимости реализации SSL/TLS-протокола


Ну и конечно же нельзя забывать о том, что даже если в реализации SSL/TLS-протокола нет логических ошибок (если конечно кто-то в это ещё верит), то защиту можно обойти посредством похищения приватного ключа [12], посредством применения 0day-эксплойтов к таким вещам как клавиатуры, браузеры, операционные системы, утилиты и прошивки [3]; посредством компрометации BGP-маршрутизации [10]; или атаковать SSL/TLS по аппаратным (см. рис. 3) [8] и/или программным [9] обходным каналам.


Рисунок 3. Атака на SSL по аппаратным обходным каналам


Кроме того, злоумышленники могут выполнять практически невидимые MiTM-атаки, злоупотребляя механизмом кэширования SSL/TLS-сеансов, реализованным в классе SSLSessionCache. Этот механизм проверяет достоверность сертификатов только при первоначальном соединении; и при этом не способен должным образом аннулировать сеанс связи после удаления сертификатов с устройства. Кроме того, после перезагрузки Android-устройства (через опции «Перезапуск» или «Отключить питание») можно продолжать видеть зашифрованный трафик некоторых приложений, которые после перезагрузки не запускались, но до перезагрузки работали. Так например с Google Maps происходит. В [2] описано, как благодаря этим недочётам кэширования, злоумышленник может совершенно прозрачно для пользователя устанавливать и удалять «невидимые сертификаты», и затем устанавливать сетевое соединение с любым приложением.


Рисунок 4. Ретроспектива уязвимого шифрования


Среди других распространённых уязвимостей реализации SSL/TLS-протокола можно отметить уязвимое шифрование (см. рис. 4) [5], повторное использования GCM (Galois/Counter Mode; счётчик с аутентификацией Галуа) [6], хитрость с CNG (CryptoAPI-NG) в Schannel (см. рис. 5) [7], некорректную проверку цепочки доверия [2], некорректную проверка имени хоста [11].


Рисунок 5. Хитрость с CNG: вытягиваем секреты из Schannel


Некорректная проверка цепочки доверия представляет собой ситуацию, когда веб-приложение принимает абсолютно любой сертификат, в котором указано корректное имя хоста, не проверяя при этом каким центром сертификации он был подписан. Это позволяет перехватывать и дешифровывать пароли и/или номера кредитных карт. А в некоторых случаях даже делать инъекцию вредоносного кода. В Android-софт данная уязвимость проникает, например когда создаётся кастомизированный X509TrustManger-интерфейс, который игнорирует исключения CertificateException. Или когда разработчик софта вставляет в код WebViews-компонента вызов метода SslErrorHandler.proceed(). [2]


Некорректная проверка имени хоста представляет собой ситуацию, когда веб-приложение принимает сертификат, не удостоверившись в том, что хост, с которого этот сертификат пришёл, присутствует в списке доверенных хостов. В Android-софт данная уязвимость проникает, например когда создаётся HostnameVerifier-интерфейс, который при любых условиях возвращает TRUE. Или когда разработчик софта в вставляет в код WebViews-компонента вызов метода SslErrorHandler.proceed(). [2]


Коренная причина существования уязвимостей в SSL/TLS-протоколе


Коренная причина существования подавляющего большинства перечисленных уязвимостей – ужасный API-дизайн SSL/TLS-библиотек (в том числе JSSE, OpenSSL, и GnuTLS). А также не менее ужасный дизайн библиотек передачи данных (таких как cURL, Apache HttpClient и urllib), каждая из которых представляет собой высокоуровневую обёртку для SSL/TLS-библиотек. Не говоря уже о middleware-сервисах (таких как Apache Axis, Axis 2, или Codehaus XFire), которые ещё более высокоуровневой обёрткой являются, и которые увеличивают «снежный ком» ужасного дизайна ещё больше. Вместо того чтобы вести с прикладным разработчиком (зачастую далёким от системного программирования) диалог на понятном ему языке (в терминах конфиденциальности и аутентификации), абстрагировано от низкоуровневых подробностей реализации SSL/TLS-протокола, эти API вываливают на бедолагу кучу низкоуровневых SSL/TLS-параметров, непонятных ему. Требуют от высокоуровневого софта, чтобы он правильно выставлял низкоуровневые опции; реализовывал функции проверки имени хоста и заботился об интерпретации возвращаемых низкоуровневыми операциями значений.


В результате, прикладные разработчики используют SSL/TLS API неправильно: ошибочно интерпретируют многообразие их параметров, опций, побочных эффектов и возвращаемых значений. Например [1]:


  • Amazon’овская PHP-библиотека Flexible Payments Service пытается включить проверку имени хоста – посредством установки параметра CURLOPT_SSL_VERIFYHOST в значение TRUE (в cURL-библиотеке). Однако корректное значение по умолчанию для этого параметра – 2; если же присвоить ему значение TRUE, то этому параметру незаметно для разработчика присваивается значение 1, и т.о. проверка сертификата отключается.
  • PHP-библиотека PayPal Payments Standard – приобрела ту же самую ошибку; причём в тот момент, когда предыдущая, уязвимая, реализация обновлялась (т.е. одну ошибку убрали, другую добавили).
  • Другой пример – это Lynx, тексто-ориентированный браузер. Он проверяет самоподписанные сертификаты, – но только в том случае, если GnuTLS’ная функция проверки сертификата возвращает отрицательное значение. Однако эта самая функция для некоторых ошибок возвращает 0; в том числе в тех случаях, когда сертификаты подписаны недоверенным органом. Из-за этого цепочка проверки доверия в Lynx – нарушена.

Кроме того, прикладные разработчики зачастую неправильно понимают, какие именно гарантии безопасности предоставляет та или иная SSL/TLS-библиотека. Поэтому в дикой природе можно встретить клинические случаи, когда в приложениях, принципиально нуждающихся в защищённых коммуникациях (например, взаимодействующих с платёжным процессором), используется SSL/TLS-библиотека, которая не проверяет SSL/TLS-сертификаты вообще. Более прозаичные, но ещё более убийственные случаи – это когда разработчик какого-нибудь из промежуточных слоёв софта молча отключает процедуру проверки SSL/TLS-сертификатов (он это может сделать, например, для тестирования системы, а после тестирования – забыть вновь включить её). При этом высокоуровневый программный код, использующий этот промежуточный слой, уверен, что проверка сертификатов производится. Т.о. ошибки SSL/TLS часто бывают скрыты в глубине одного или сразу нескольких промежуточных слоёв-библиотек – из-за чего обнаружить данную проблему становится практически невозможно.


Например, в JSSE (Java Secure Socket Extension) расширенный интерфейс SSLSocketFactory API – молча пропускает проверку имени хоста, если поле «algorithm» в SSL-клиенте установлено в NULL или в пустую строку, – а не в HTTPS. Хотя данное обстоятельство упоминается в справочном руководстве JSSE, многие Java-реализации SSL-протоколов используют SSLSocketFactory без выполнения проверки имени хоста…


Ложка мёда в бочку дёгтя


Т.о., по факту получается, что в большинстве современного небраузерного веб-софта проверка SSL/TLS-сертификатов либо отключена совсем, либо реализована неправильно. На рисунке 7 представлена классификация актуальных уязвимостей SSL/TLS-протокола. Некоторые из этих уязвимостей, но не все, были описаны и/или упомянуты выше. Ознакомиться с упомянутыми, но неописанными уязвимостями – можно почитав материалы, перечисленные в библиографии.


Рисунок 6. Классификация актуальных для SSL/TLS уязвимостей


Ну и чтобы добавить ложку мёда в бочку дёгтя, стоит отметить, что в [1] подробно/понятно/популярно/грамотно описано, как SSL реализовываться должен, со ссылкой на RFC. Лучшего описания, которое бы технически точным и одновременно понятным было, мы не встречали. Также в [1] разбираются самые распространённые SSL-библиотеки, с классификацией по уровню абстрагирования (низкоуровневые/высокоуровневые). Всё с диаграммами и лаконичными алгоритмами в псевдокоде. Подробно уязвимости конкретных продуктов описаны, с приведением программного кода некорректного, и указанием ошибок. Так что если вдруг у кого-то в очередной раз возникнет желание создать такую реализацию SSL/TLS-фреймворка, которая станет исключением из поговорки «хотели как лучше, а получилось как всегда», то [1] – идеальное для этого начало.


Библиография

1. Martin Georgiev, Rishita Anubhai, Subodh Iyengar. The Most Dangerous Code in the World: Validating SSL Certificates in Non-Browser Software // Proceedings of the 2012 ACM conference on computer and communications security. 2012. pp. 38-49.
2. Tony Trummer. Mobile SSL Failures // Proceedings of the HITB Security Conference. 2015.
3. Kellen Evan Person. How Ciphersuites Work: TLS in Pieces // 2017.
4. Catalin Cimpanu. Extended Validation (EV) Certificates Abused to Create Insanely Believable Phishing Sites // BleepingComputer. 2017.
5. David Adrian. A Retrospective on the Use of Export Cryptography // Black Hat. 2016.
6. Sean Devlin. Nonce-Disrespecting Adversaries: Practical Forgery Attacks on GCM in TLS // Black Hat. 2016.
7. Jake Kambic. Cunning with CNG: Soliciting Secrets from Schannel // Black Hat. 2016.
8. Valeria Bertacco. Torturing OpenSSL // Black Hat. 2012.
9. Tom van Goethem. HEIST: HTTP Encrypted Information Can Be Stolen Through TCP-Windows // Black Hat. 2016.
10. Artyom Gavrichenkov. Breaking Https With BGP Hijacking // Black Hat. 2016.
11. Chris Stone, Tom Chothia. Spinner: Semi-Automatic Detection of Pinning without Hostname Verification // Proceedings of the Annual Computer Security Applications Conference (ACSAC) 2017.
12. Marco Ortisi. Recover a RSA Private Key from a TLS Session with Perfect Forward Secrecy // Black Hat. 2016.


PS. Первоначально статья была опубликована на «Хакере».

Комментарии (6)


  1. gecube
    05.06.2019 08:39

    Очень интересный обзор. Но решение-то проблемы какое? Взять весь софт и переписать? Внимательнее читать документацию? Ну, не сработает. В результате имеем то, что имеем


  1. atoro
    05.06.2019 09:01

    Я вот как раз с неделю назад темой увлёкся, увидел заголовок обрадовался, но статья разочаровала если честно. Алармности много, «разбор уязвимостей» по тексту вообще нет. Перечитал добросовестно все указанные источники. Осталось впечатление второй свежести. В 2019 году приводить в качестве аргумента уязвимости тот факт, что в 2012 на SSL/TLS повсеместно забивали сами разработчики такое себе решение. Как и ссылаться на уязвимость TLS < 1.2 во времена повсеместного TLS 1.3. Но в любом случае спасибо — пара моментов из разряда смутных подозрений перешли в уверенность.


  1. mmMike
    05.06.2019 09:15

    Мне показалось или основная проблема состоит в том что кодер (не разработчик, а "кодер" в худшем смысле этого слова) не понимает, что делает и из-за этого возникают проблемы.
    Смысл кивать на библиотеки, которые "позволяют слишком много и слишком много нужно читать на них документацию".


    Типично: "Я тут 3 месяца походил на курсы Java и теперь я программист".
    И начинается потом "магия"… Магия SpringBoot, магия TLS, магия TCP/IP стека и прочая магия.


    1. gecube
      05.06.2019 10:19

      А потом магия внезапно кончается (мы же в инженерном мире) :-) и вся машинерия перестает работать. Наверное, нужно больше спайса :-)


  1. lexamor
    05.06.2019 11:34

    Да, актуальность сомнительная…
    Меня, например, заинтересовал момент с CURLOPT_SSL_VERIFYHOST.
    И что же по этому поводу говорит документация CURL-a:
    «When the verify value is 1, curl_easy_setopt will return an error and the option value will not be changed. It was previously (in 7.28.0 and earlier) a debug option of some sorts, but it is no longer supported due to frequently leading to programmer mistakes. Future versions will stop returning an error for 1 and just treat 1 and 2 the same.»
    P.S. 7.28.1 Release date: Nov 20 2012


  1. Revertis
    05.06.2019 15:14

    Что-то слишком много воды и фантастики типа «злоумышленники могут… (а кто ещё в этом сомневается?)». Где конкретика? Что вы понимаете под "процедура проверки SSL/TLS-сертификата, – и даже EV-SSL, сертификата с расширенной проверкой [4], – полностью провальная"? То, что либы доверяют списку ЦА из ОС? А вы предлагаете везде внедрять серт-пиннинг?