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

Отладка защищенных по 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.
Нам понадобится:
Поскольку одним из главных источников информации для нас будут являться логи, перво-наперво нужно правильно настроить их получение. Вполне рабочим вариантом будет опция JVM javax.net.debug=ssl:handshake:data. Сразу оговоримся, что именно такое значение она иметь не обязана, можно (наверно) обойтись и универсальным javax.net.debug=all, но работать с результатами такого выбора может быть сложновато (объем логов может оказаться гигантским). Наш же выбор объясняется следующим:
Наличие такой опции должно обеспечить нам вывод в лог (или стандартный вывод) отладочной информации, к которой мы вернемся чуть позже.
Снюхивать трафик целевого приложения Wireshark'ом можно лишь после простановки указанной выше опции, так как в противном случае мы не будем обладать сессионными ключами. К тому же надо помнить, что ключи «эфемерны» — они пригодны лишь для одной SSL-сессии, то есть лог от одного сеанса связи не подойдет для дешифрации трафика другого сеанса. Ну и чтобы дышалось совсем легко, рекомендую сразу при старте снифера указать хост, с которым планируется обмен данными; это позволит еще «на берегу» отбросить ненужные пакеты, проходящие через прослушиваемый сетевой интерфейс:

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

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

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

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

Теперь нужно добавить это значение к формируемой нами строке NSS-файла и «причесать» строку так, чтобы получилось что-то вроде этого (комментарии в стандартной нотации вполне допустимы):
# SSL/TLS secrets log file, generated by Toparvion
RSA 75ff866e23beca1c 03012aede74befa88233253e3207bb1320935ab206696512674df5c6dee7dfaa2156932bc559631c8f3bb46ae38a71ff
Тем, для кого RSA — «как раз тот случай» (как правило, это приложения до Java 7), уже можно переходить к разделу «Дегустация». Тем же, кому довелось столкнуться с PFS (зачастую это Java 7 и выше), придется читать дальше…
Аналогично методу RSA, строка для дешифрации записей на основе PSF должна состоять из трех элементов, разделенных пробелом:
Источники данных для второго и третьего элемента также аналогичны предыдущему методу, но есть некоторые тонкости. Клиентское случайное число является конкатенацией текущего времени в миллисекундах по GMT и собственно случайного значения. В случае обращения к Wireshark это отчетливо видно:

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

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

После извлечения главного ключа из логов, дополняем им формируемую строку, «причесываем» и получаем нечто вроде:
# SSL/TLS secrets log file, generated by Toparvion
CLIENT_RANDOM 551435582740bdc1386b20b7fcb51428fe3042e06c8e6e94c910786f577a2ada 976dc1d54dd74d3c2e715109c8a4fb8e743efc084614abc0e12fdb78e472c30e3590ac5eb383424b2d8fa3de84c8b0f5
Нельзя не заметить, что Wireshark весьма чувствителен к формату NSS-файла, поэтому лучше уже сейчас тщательно перепроверить, сходится ли число байт в каждом элементе строки и нет ли где-либо лишних пробелов; это может сэкономить время в будущем.
Теперь, когда все «ручные» шаги выполнены, пора дать слово Wireshark'у — преподносим ему созданный файл в точности также, как это описано в упомянутой в начале статье [1]:
Нажимаем OK и смотрим на изменения в перехваченном трафике:

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

Как видно, несмотря на обращение по 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/
Нажмите здесь для печати.