- PVSM.RU - https://www.pvsm.ru -

Дешифрация TLS трафика Java приложений с помощью логов

Дешифрация TLS трафика Java приложений с помощью логов - 1

Отладка защищенных по SSL/TLS интеграций у Java приложений порой становится весьма нетривиальной задачей: соединение не ставится/рвется, а прикладные логи могут оказаться скудными, доступа к правке исходных кодов может не быть, перехват трафика Wireshark'ом и попытка дешифрации приватным ключом сервера (даже если он есть) может провалиться, если в канале применялся шифр с PFS; прокси-сервер вроде Fiddler или Burp может не подойти, так как приложение не умеет ходить через прокси или на отрез отказывается верить подсунутому ему сертификату…

Недавно на Хабре появилась публикация [1] от ValdikSS [2] о том, как можно с помощью Wireshark расшифровать любой трафик от браузеров Firefox и Chrome без обладания приватным ключом сервера, без подмены сертификатов и без прокси. Она натолкнула автора нынешней статьи на мысль — можно ли применить такой подход к Java приложениям, использовав вместо файла сессионных ключей отладочные записи JVM? Оказалось — можно, и сегодня, уважаемые однохабряне, я расскажу, как это сделать.

Идея рецепта

С недавних версий браузеры Firefox и Chrome научились выводить в специально задаваемый файлик данные, достаточные для деривации (получения) сессионных ключей, которыми шифруется передаваемый ими трафик (равно как и принимаемый трафик, поскольку внутри SSL/TLS используется симметричное шифрование). Строго говоря, делают это не сами браузеры, а библиотечка формат [3] записываемых файлов. Этот формат умеет читаться и использоваться Wireshark'ом, чтобы расшифровывать SSL-записи, зашифрованные соответствующими ключами. Идея нашего «блюда» заключается в том, чтобы с той же целью научиться формировать подобные файлы самостоятельно для Java-приложений, опираясь в качестве источника на отладочные логи, которые пишутся в стандартный вывод при наличии JVM-опции javax.net.debug.

Ингредиенты

Нам понадобится:

  • Java-приложение, для которого мы можем задавать параметры запуска (опции JVM).
    Для определенности полагаем, что приложение при установлении соединения выступает в роли клиента.
    Версию JDK (JRE) желательно иметь 1.6 или 1.7, на других (пока) не проверялось;
  • Wireshark версии 1.6.0 или выше;
  • Текстовый редактор, например, Notepad++;
  • Щепотка терпения, внимательности и времени.

Замес

Параметры запуска

Поскольку одним из главных источников информации для нас будут являться логи, перво-наперво нужно правильно настроить их получение. Вполне рабочим вариантом будет опция JVM javax.net.debug=ssl:handshake:data. Сразу оговоримся, что именно такое значение она иметь не обязана, можно (наверно) обойтись и универсальным javax.net.debug=all, но работать с результатами такого выбора может быть сложновато (объем логов может оказаться гигантским). Наш же выбор объясняется следующим:

  1. ssl — чтобы в лог писались сообщения, касающиеся только SSL;
  2. handshake — чтобы видеть каждое сообщение в рамках главного для нас этапа — handshake;
  3. data — для ленивых, чтобы вручную не переводить некоторые значения из десятеричной системы счисления в шестнадцатеричную (hex);

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

Настройка снифера

Снюхивать трафик целевого приложения Wireshark'ом можно лишь после простановки указанной выше опции, так как в противном случае мы не будем обладать сессионными ключами. К тому же надо помнить, что ключи «эфемерны» — они пригодны лишь для одной SSL-сессии, то есть лог от одного сеанса связи не подойдет для дешифрации трафика другого сеанса. Ну и чтобы дышалось совсем легко, рекомендую сразу при старте снифера указать хост, с которым планируется обмен данными; это позволит еще «на берегу» отбросить ненужные пакеты, проходящие через прослушиваемый сетевой интерфейс:

Дешифрация TLS трафика Java приложений с помощью логов - 2
Съём и запись

После задания нужных параметров запуска приложения и настроек снифера, можно стартовать само приложение и включать захват пакетов в Wireshark. Тогда при попытке приложения выйти на (защищенную) связь с каким-либо сервером наши «кастрюльки» начнут заполняться. С точки зрения Wireshark это будет выглядеть примерно так:

Дешифрация TLS трафика Java приложений с помощью логов - 3

Как видно, Wireshark явно определяет некоторые SSL-записи как зашифрованные; такими же являются и записи с типом Application Data.

А с позиций стандартного вывода (логов) приложения — примерно так:

...
*** ClientHello, TLSv1
RandomCookie:  GMT: 1427238714 bytes = { 246, 5, 6, 214, 168, 159, ... , 140, 141, 50, 196 }
Session ID:  {}
Cipher Suites: [SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, ..., SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA]
Compression Methods:  { 0 }
...

На самом деле в лог будет выведено еще много всякого (формат отладочного вывода нигде не специфицирован и меняется от версии Java к версии), но нам пока достаточно увидеть хотя бы это.

«Расстойка»

Обладая отладочными записями от сеанса связи по SSL/TLS, мы можем сформировать файл сессионных ключей в формате NSS. Для этого нам в первую очередь потребуется определить, какой метод распространения сессионных ключей использовался в нашем сеансе связи: метод обмена (aka RSA) или метод генерации (aka DH или PFS, хоть это и разные вещи). В чем их суть и отличия можно почитать в замечательной работе Sally Vandeven'а [4]. Нам же достаточно знать лишь сам метод, а определить его можно, как минимум, двумя путями:

  1. По названию шифра, выведенному в лог или определенному снифером. Например, по такому выводу
    *** ServerHello, TLSv1
    RandomCookie:  GMT: 1037995915 bytes = { 168, 183, ... 204, 178 }
    Session ID:  {141, 155, ... 214, 36}
    Cipher Suite: SSL_RSA_WITH_RC4_128_SHA
    ...

    видно, что шифр не имеет в названии никаких упоминаний DH, зато явно называет себя RSA. Однако такая ясность присутствует далеко не всегда, поэтому можно воспользоваться планом Б:

  2. По наличию SSL-сообщения ServerKeyExchange в перехвате трафика в Wireshark (см. скриншот в подразделе «Съём и запись») — оно присутствует для методов DH и отсутствует для RSA (объяснение почему — за рамками данной статьи). Безусловно, наличие этого сообщения можно определить и по тем же логам.

Определив метод распространения сессионных ключей, обратимся к описанию формата [3]файлов NSS, который предписывает нам различать строки каждого файла как раз по этим двум методам. Рассмотрим каждый из них по-подробнее.
Для начала предположим, что в нашем сеансе связи использовался какой-либо шифр на основе RSA. Согласно описанию формата, соответствующая строка файла должна начинаться с текста RSA, за которым через пробел должны последовать 16 байт HEX-кодированного шифрованного ключа PreMasterSecret, а еще через пробел — 96 байт HEX-кодированного нешифрованного ключа PreMasterSecret (т.е. его же). Этот ключ является основой для генерации главного ключа — MasterSecret и передается от клиента к серверу в сообщении ClientKeyExchange, будучи зашифрованным публичным ключом сервера. Это значит, что первую часть строки (шифрованное представление этого ключа) должно быть видно в Wireshark. Находим нужное сообщение и убеждаемся — да, он есть:

Дешифрация TLS трафика Java приложений с помощью логов - 4

Заметка для буквоедов

Опытный хабровед вправе прервать повествование вопросом — почему в формате файла NSS требуется лишь 16 байт, в то время как длина зашифрованного ключа составляет 256 байт?
Это объясняется тем, что шифрованное значение используется Wireshark'ом просто как индекс — оно нужно лишь для того, чтобы из потенциального множества строк в NSS-файле найти именно ту запись, в которой содержится подходящий MasterSecret. Сделать это можно путем последовательного сопоставления шифрованной версии этого ключа (взятой из перехваченного трафика) с первым (после «RSA») элементом каждой строки файла. Собственно, это Wireshark и делает, и сопоставлять ключ по всей длине в этом случае вовсе необязательно, вполне хватает и 16-ти байт.

К слову, это же значение можно получить и из логов приложения, и здесь нам как раз пригодится подзначение JVM-опции ":data":

Дешифрация TLS трафика Java приложений с помощью логов - 5

Найденное значение (16 байт) можно вставлять в формируемый NSS-файл. Теперь провернем аналогичную операцию для второго элемента строки — собственного значения ключа PreMasterSecret. Поскольку оно, очевидно, никогда не передается по сети в открытом виде (собственно, поэтому оно и называется ...Secret), выуживать его придется только из логов. Благо, с недвусмысленными подсказками от JVM сделать это не особо сложно:

Дешифрация TLS трафика Java приложений с помощью логов - 6

Теперь нужно добавить это значение к формируемой нами строке NSS-файла и «причесать» строку так, чтобы получилось что-то вроде этого (комментарии в стандартной нотации вполне допустимы):

# SSL/TLS secrets log file, generated by Toparvion
RSA 75ff866e23beca1c 03012aede74befa88233253e3207bb1320935ab206696512674df5c6dee7dfaa2156932bc559631c8f3bb46ae38a71ff

Тем, для кого RSA — «как раз тот случай» (как правило, это приложения до Java 7), уже можно переходить к разделу «Дегустация». Тем же, кому довелось столкнуться с PFS (зачастую это Java 7 и выше), придется читать дальше…

Аналогично методу RSA, строка для дешифрации записей на основе PSF должна состоять из трех элементов, разделенных пробелом:

  1. Тип записи; в данном случае он должен быть равен CLIENT_RANDOM;
  2. 64 байт HEX-кодированного клиентского случайного числа Random;
  3. 96 байт HEX-кодированного главного ключа MasterSecret;

Источники данных для второго и третьего элемента также аналогичны предыдущему методу, но есть некоторые тонкости. Клиентское случайное число является конкатенацией текущего времени в миллисекундах по GMT и собственно случайного значения. В случае обращения к Wireshark это отчетливо видно:

Дешифрация TLS трафика Java приложений с помощью логов - 7

А вот при обращении к логам легко ошибиться, но можно пользоваться вот такой подсказкой:

Дешифрация TLS трафика Java приложений с помощью логов - 8

Здесь также вхождение ":data" в значении JVM-опции javax.net.debug избавляет нас от необходимости ручной конвертации систем счисления. Всего нам потребуется 64 байта случайного числа, то все оно целиком (а не только начало, как было с RSA). Оно будет также играть роль индекса при поиске Wireshark'ом подходящей записи в NSS-файле.
Третий элемент строки — главный секретный ключ MasterSecret — также может быть извлечен из логов приложения:

Дешифрация TLS трафика Java приложений с помощью логов - 9

После извлечения главного ключа из логов, дополняем им формируемую строку, «причесываем» и получаем нечто вроде:

# SSL/TLS secrets log file, generated by Toparvion
CLIENT_RANDOM 551435582740bdc1386b20b7fcb51428fe3042e06c8e6e94c910786f577a2ada 976dc1d54dd74d3c2e715109c8a4fb8e743efc084614abc0e12fdb78e472c30e3590ac5eb383424b2d8fa3de84c8b0f5

Нельзя не заметить, что Wireshark весьма чувствителен к формату NSS-файла, поэтому лучше уже сейчас тщательно перепроверить, сходится ли число байт в каждом элементе строки и нет ли где-либо лишних пробелов; это может сэкономить время в будущем.

Дегустация

Теперь, когда все «ручные» шаги выполнены, пора дать слово Wireshark'у — преподносим ему созданный файл в точности также, как это описано в упомянутой в начале статье [1]:

  1. Открываем в Wireshark контекстное меню на любом SSL/TLS пакете;
  2. Выбираем Protocol Preferences -> Secure Socket Layer Preferences...;
  3. В открывшемся окне в графе (Pre-)Master Secret log filnename указываем путь к сформированному нами NSS-файлу.

Нажимаем OK и смотрим на изменения в перехваченном трафике:

Дешифрация TLS трафика Java приложений с помощью логов - 10

Если дешифрация выполнена успешно, то пакеты, ранее имевшие в названиях слово Encrypted, обретут конкретные имена. Именно так стало с командами Finished, приведенными на рисунке выше.
Кроме того (и это, пожалуй, самый приятный момент) теперь можно выбрать любой пакет с протоколом SSL или TLS и в его контекстном меню щелкнуть на Follow SSL Stream — результат не должен требовать комментариев:

Дешифрация TLS трафика Java приложений с помощью логов - 11

Как видно, несмотря на обращение по HTTPS, мы видим передаваемый трафик и можем экспортировать его для дальнейшего анализа.

Если все же не получилось...

Ошибок на этом скользком пути можно сделать множество. Одним из самых ценных источников информации является лог самого Wireshark'а — он ведется в том случае, если будет указан путь к файлу лога в графе SSL denug file все в том же окне настроек SSL.
Важно отметить, что никакой фильтрации на лог не предусмотрено, а Wireshark весьма подробен, поэтому если держать лог постоянно включенным, он, во-первых, очень быстро вырастет, во-вторых, может привести к торможениям самого Wireshark'а (т.к., видимо, пишется синхронно). В связи с этим, лучше всего указывать файл лога лишь перед самым применением NSS-файла, а по окончании анализа — чистить его (но не удалять).

Заключение

В статье был рассмотрен еще один подход к дешифрации SSL/TLS трафика Java приложений с целью их отладки.
В приведенном виде подход едва ли применим на практике, так как требует существенных затрат времени и наличия определенных знаний и навыков у исполнителя. Однако представленное описание позволит формализовать, а значит, и автоматизировать (запрограммировать) этот подход и, таким образом, поставить его на службу людям. Такая работа уже начата автором. Если эта затея интересна и вам — милости просим! Не забудьте рассказать об этом на Хабре.

Спасибо за чтение!

Автор: Toparvion

Источник [5]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/java/87147

Ссылки в тексте:

[1] появилась публикация: http://habrahabr.ru/post/253521/

[2] ValdikSS: http://habrahabr.ru/users/valdikss/

[3] формат: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format

[4] работе Sally Vandeven'а: http://www.sans.org/reading-room/whitepapers/authentication/ssl-tls-whats-hood-34297

[5] Источник: http://habrahabr.ru/post/254205/