Добрый день, дорогие читатели Хабра!

Мы команда специалистов из компании ПМ. Довольно часто к нам приходят заказы на анализ защищенности или тестирование на проникновение веб-ресурсов. Первоначальным этапом при проведении работ является разведка, которая включает в себя анализ принципов работы исследуемого веб-ресурса, обнаружение используемых технологий, окружения и т.д. Одним из методов для осуществления данной задачи является исследование пакетов, отправляемых между веб-клиентом и веб-сервером. Иногда исследование пакетов не составляет особого труда, но бывают случаи, когда это становится нетривиальной задачей. В ситуации, когда речь идет об открытом (незашифрованном) трафике, можно элементарно воспользоваться любым пакетным анализатором, типа Wireshark. Однако в тех случаях, когда применяется шифрование, приходится использовать различные методы для расшифровки. Именно они и будут рассмотрены в данной статье.

1. Настройка расшифровки трафика при помощи перехватывающего прокси

1.1. Обычный прокси

В качестве перехватывающего прокси рассмотрим Burp Suite. При его запуске автоматически запускается листенер на 127.0.0.1:8080. Чтобы пропустить трафик через него, нужно указать утилите этот адрес. К примеру, так это будет для утилиты curl:

curl --proxy 127.0.0.1:8080 https://google.com

Перехваченный curl запрос и ответ на него в Burp Suite
Перехваченный curl запрос и ответ на него в Burp Suite

Стоит отметить, что так как сертификат Burp Suite по умолчанию не является доверенным, то потребовалось отключить проверку сертификата при помощи опции -k. Альтернативой этому может служить добавление CA сертификата в список доверенных. Скачать его можно, если перейти по адресу http://127.0.0.1:8080/. В случае с curl, чтобы добавить сертификат в список доверенных нужно перевести скаченный cacert.der в cacert.crt:

openssl x509 -in cacert.der -inform DER -out cacert.crt

После этого можно добавить сертификат:

sudo cp cacert.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

1.2. Прозрачный прокси

В случае, если клиент не может использовать прокси (например, если у используемого ПО нет поддержки прокси в настройках), в Burp Suite есть возможность настроить так называемый прозрачный прокси.

Установка порта для листенера
Установка порта для листенера
Установка флага для поддержки прозрачного прокси
Установка флага для поддержки прозрачного прокси

При этом варианте трафик направляется листенеру Burp Suite, который после этого делает запрос на нужный ресурс. Есть несколько методов как можно настроить невидимый прокси.

1.1.1. Изменение разрешения DNS

Изменяя трансляцию доменных имен через файл /etc/hosts можно перенаправить трафик на листенер невидимого прокси:

Содержание файла /etc/hosts
Содержание файла /etc/hosts

После того как пакет поступил на листенер, Burp Suite перенаправляет его, основываясь на заголовке Host. Для того, чтобы Burp Suite доставил пакеты туда, куда мы бы хотели их доставить, можно воспользоваться следующими методами:

1.   Изменить разрешение доменных имен в самом Burp Suite

Настройка разрешений доменных имен в Burp Suite
Настройка разрешений доменных имен в Burp Suite

2. Перенаправить трафик в опциях листенера. При таком варианте нужно для каждого ip создавать виртуальный интерфейс командой:

sudo ifconfig lo:0 127.0.0.2 netmask 255.255.255.0 up

Такой вариант подойдет, если в запросе отсутствует заголовок Host, например, если используется протокол ниже HTTP/1.1

Установка порта и интерфейса для листенера
Установка порта и интерфейса для листенера
Установка перенаправления запросов на нужный адрес
Установка перенаправления запросов на нужный адрес

1.1.1. Перенаправление трафика через iptables

При помощи iptables можно перенаправить исходящий трафик на листенер Burp Suite. Однако, следует учитывать, что нужно каким-то образом пропускать трафик, который отправляет сам Burp, в противном случае произойдет зацикливание. Это можно решить, если создать пользователя (например, burp) и потом запустить от него Burp Suite:

adduser burp

После этого можно создать правило iptables:

sudo iptables -t nat -A OUTPUT -m owner ! --uid-owner burp -p tcp --dport 443 -j DNAT --to-destination 127.0.0.1:443

Стоит отметить, что для возможности запуска Burp Suite, нужно добавить пользователю burp доступ к X-серверу командой:

xhost -si:localuser:burp

1.1.1. Вывод

В первом разделе мы рассмотрели основные способы проксирования трафика через Burp Suite, а именно:

Если никаких проблем, то нам достаточно настройки обычного прокси.

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

Далее будут рассмотрены методы расшифровки трафика при помощи ключей шифрования

2. Настройка расшифровки трафика при помощи ключей шифрования

При данном методе расшифровки трафика требуется выполнить 3 задачи:

  1. Извлечь ключи шифрования из исследуемой утилиты.

  2. Захватить сетевой трафик.

  3. Расшифровать захваченный трафик, используя извлеченные ключи шифрования.

Задачи захвата и расшифровки трафика выполняет анализатор трафика Wireshark. Согласно странице wiki, существует два метода для расшифровки: используя приватный ключ RSA или сеансовые секреты (per-sess ion secrets).

Метод с сеансовым ключом является универсальным и работает, даже когда генерация общего секрета происходит при помощи протокола Диффи-Хеллмана. Однако, для его применения необходимо постоянно записывать сеансовые секреты в файл. Некоторые программы, например, браузеры Firefox и Chrome, записывают сеансовые секреты по пути, который указан в переменной окружения SSLKEYLOGFILE. Формат логов:

<Label><space><ClientRandom><space><Secret>, где

<Label> название секрета.

<ClientRandom> 32 байтное случайное значение, получаемое из сообщения Client Hello, представляется в виде 64 шестнадцатеричных символов.

Пример Client Hello в перехваченном Wireshark пакете
Пример Client Hello в перехваченном Wireshark пакете

<Secret> само значение секрета, формат зависит от названия <Label>

Возможные значения <Label>:

  • RSA: 48 bytes for the premaster secret, encoded as 96 hexadecimal characters (removed in NSS 3.34).

  • CLIENT_RANDOM: 48 bytes for the master secret, encoded as 96 hexadecimal characters (for SSL 3.0, TLS 1.0, 1.1 and 1.2).

  • CLIENT_EARLY_TRAFFIC_SECRET: the hex-encoded early traffic secret for the client side (for TLS 1.3).

  • CLIENT_HANDSHAKE_TRAFFIC_SECRET: the hex-encoded handshake traffic secret for the client side (for TLS 1.3).

  • SERVER_HANDSHAKE_TRAFFIC_SECRET: the hex-encoded handshake traffic secret for the server side (for TLS 1.3).

  • CLIENT_TRAFFIC_SECRET_0: the first hex-encoded application traffic secret for the client side (for TLS 1.3).

  • SERVER_TRAFFIC_SECRET_0: the first hex-encoded application traffic secret for the server side (for TLS 1.3).

  • EARLY_EXPORTER_SECRET: the hex-encoded early exporter secret (for TLS 1.3).

  • EXPORTER_SECRET: the hex-encoded exporter secret (for TLS 1.3).

Программы, которые не поддерживают логирование сеансовых ключей, следует настраивать индивидуально. К примеру, для java программ можно использовать jSSLKeyLog, extract-tls-secrets.

Если программа использует библиотеку openssl, то извлечь сеансовые ключи можно используя отладчик (например gdb), через LD_PRELOAD или путем добавления в исходный код openssl логирования сенсовых ключей и пересборки пакета. Далее рассмотрим каждый из этих методов в изолированной среде Docker.

2.1. Извлечение ключей через gdb

Для сборки пакета libssl с отладочными символами через apt, нужно добавить в sources.list архивы с исходными кодами и загрузить информацию о пакетах через apt update.

sources.list
deb http://archive.ubuntu.com/ubuntu/ focal main restricted

deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted

deb http://archive.ubuntu.com/ubuntu/ focal universe
deb http://archive.ubuntu.com/ubuntu/ focal-updates universe

deb http://archive.ubuntu.com/ubuntu/ focal multiverse
deb http://archive.ubuntu.com/ubuntu/ focal-updates multiverse

deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse


deb http://security.ubuntu.com/ubuntu/ focal-security main restricted
deb http://security.ubuntu.com/ubuntu/ focal-security universe
deb http://security.ubuntu.com/ubuntu/ focal-security multiverse

deb-src http://archive.ubuntu.com/ubuntu focal main restricted universe multiverse
deb-src http://archive.ubuntu.com/ubuntu focal-updates main restricted universe multiverse
deb-src http://security.ubuntu.com/ubuntu focal-security main restricted universe multiverse

После этого нужно установить необходимые для сборки зависимости через apt-get build-dep libssl1.1 и начать сборку через команду apt-get --build source libssl1.1, предварительно установив переменную окружения DEB_BUILD_OPTIONS в значение debug notest nostrip noopt nocheck. Теперь нужно установить собранный пакет через команду (название пакета может отличаться) apt-get install --reinstall ./libssl1.1_1.1.1f-1ubuntu2.12_amd64.deb  и перейти в директорию openssl-1.1.1f, которая была создана при сборке и запустить отладку программы, использующей openssl. В данном случае рассмотрим отладку утилиты curl.

Для автоматизации данных действий создадим в отдельной директории Dockerfile:

Dockerfile
FROM ubuntu:18.04 
SHELL ["/bin/bash", "-c"]
COPY ./sources.list /etc/apt/sources.list
ENV TZ=Europe/Moscow
ENV DEB_BUILD_OPTIONS="debug notest nostrip noopt nocheck"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone\ 
&& apt-get update\
&& apt-get install vim -y\
&& mkdir -p /sources_build/libssl1.1\
&& cd /sources_build/libssl1.1\
&& apt-get -y build-dep libssl1.1\
&& apt-get --build source libssl1.1\
&& find -maxdepth 1 -name "*openssl*" -type d -execdir mv {} openssl1.1 \;\
&& cd ./openssl1.1\
&& dpkg-buildpackage -b \
&& find ../  -name "*libssl1.1*.deb"  -execdir mv {} libssl1.1.deb \;\
&& apt-get install --reinstall --allow-downgrades ../libssl1.1.deb -y\
&& apt-get install curl -y\
&& apt-get install gdb -y\
&& rm -rf /var/lib/apt/lists/*\
&& apt-get clean

В этой же директории поместим указанный ранее sources.list и создадим Docker образ:

docker build -t tls_debug_gdb .

Для того, чтобы в контейнере было возможно полноценно использовать gdb, его нужно запускать со следующими опциями:

docker run -it --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --name ssl_debug_gdb_cont -v /tmp:/tmp tls_debug_gdb

Перед отладкой установим в контейнере переменную окружения SSLKEYLOGFILE в /tmp/tls.log и запустим отладку.

export SSLKEYLOGFILE=/tmp/tls.log
gdb curl

Если поставить точку остановки на функцию ssl_log_secret, то можно увидеть названия сеансовых секретов, их значения, совпадающие с теми, которые записаны в /tmp/tls.log (если перевести в единый формат).

Получение значения SERVER_HANDSHAKE_TRAFFIC_SECRET в gdb
Получение значения SERVER_HANDSHAKE_TRAFFIC_SECRET в gdb
SERVER_HANDSHAKE_TRAFFIC_SECRET в /tmp/tls.log
SERVER_HANDSHAKE_TRAFFIC_SECRET в /tmp/tls.log
Сравнение значений SERVER_HANDSHAKE_TRAFFIC_SECRET из gdb и /tmp/tls.log
Сравнение значений SERVER_HANDSHAKE_TRAFFIC_SECRET из gdb и /tmp/tls.log

Чтобы автоматически записывать ключи, можно написать Python скрипт, к примеру такой:

import gdb
def start():
	gdb.Breakpoint("ssl_log_secret")
    gdb.execute("run")
    while True:
        try:
            gdb.execute("n")
            cl_rd_addr=gdb.parse_and_eval("ssl->s3->client_random").address
            cl_rd_data=gdb.selected_inferior().read_memory(cl_rd_addr, 32)
            cl_rd_data=''.join([f'{ord(i):02x}' for i in cl_rd_data])
            
            secret_addr=gdb.parse_and_eval("secret").format_string().split()[0]
            secret_data=gdb.selected_inferior().read_memory(int(secret_addr,16), 48)
            secret_data=''.join([f'{ord(i):02x}' for i in secret_data])
            
            with open("/tmp/key.log",'a') as file_:
                file_.write(' '.join([gdb.parse_and_eval("label").string(),cl_rd_data,secret_data]))
                file_.write('\n')
            
            gdb.execute("c")
        except:
            break

Данный скрипт записывает ключи в файл /tmp/key.log. Для его запуска необходимо сохранить скрипт в файл, например в /root/.gdb/keylog.py, прописать в /etc/gdbinit:

python
import sys
sys.path.insert(0, '/root/.gdb')
import keylog
end

И запустить логирование:
gdb -batch -ex "py keylog.start()" --args curl https://google.com

2.2. Извлечение ключей через пересборку пакета

Аналогично предыдущему пункту скачиваем исходные коды libssl и добавляем код в тело функции ssl_log_secret в файле ssl/ssl_lib.c, который осуществляет запись сеансовых секретов в файл, например такой:

FILE *f1 = fopen("/tmp/key.log", "a");
                if (f1) {
 
 
                fprintf(f1, "%s ",label);
		for (int n=0;n<sizeof ssl->s3->client_random;n++){
			fprintf(f1, "%02x", ssl->s3->client_random[n]);
		}
                fprintf(f1, " ");
		for (int n=0;n<secret_len;n++){
			fprintf(f1, "%02x", secret[n]);
		}
                fprintf(f1, "\n");
                fclose(f1);
        }

После этого нужно собрать и установить пакет :

dpkg-buildpackage -b
apt-get install --reinstall ../libssl1.1_1.1.1f-1ubuntu2.22_amd64.deb

2.3. Извлечение ключей через LD_PRELOAD

Для извлечения через LD_PRELOAD можно воспользоваться кодом. Перед компиляцией необходимо либо установить заголовки разработки для libbsl:

apt-get install libssl-dev

Либо раскомментировать строчки:

//#define NO_OPENSSL_102_SUPPORT
//#define NO_OPENSSL_110_SUPPORT

После компиляции:

cc sslkeylog.c -shared -o libsslkeylog.so -fPIC -ldl

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

2.4. Проверка работы на различных версиях протокола TLS

Для демонстрации работы при использовании различных версий протокола TLS, давайте развернем другой Docker контейнер с сервером Apache, поддерживающим соединение по любой версии TLS. Для включения поддержки различных версий TLS пропишем следующий конфигурационный файл:

Конфигурационный файл 000-default.conf для Apache
<VirtualHost *:443>
	ServerAdmin webmaster@localhost
	DocumentRoot /var/www/html
 
	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined
 
	SSLEngine on	
	SSLCertificateFile      /etc/ssl/certs/ssl-cert-snakeoil.pem
	SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
	SSLProtocol +TLSv1.3 +TLSv1.2 +TLSv1.1 +TLSv1
	SSLCompression off
	SSLHonorCipherOrder on
	SSLCipherSuite "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA"
	SSLCertificateFile  /etc/ssl/certs/ssl-cert-snakeoil.pem 
	SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
</VirtualHost>

Для автоматизации развертывания контейнера создадим в отдельной директории Dockerfile:

Dockerfile
FROM ubuntu:18.04
COPY ./000-default.conf /tmp/000-default.conf
RUN apt-get update\
&& apt-get install apache2 -y\
&& cp /tmp/000-default.conf /etc/apache2/sites-enabled/000-default.conf\
&& a2enmod ssl

В этой же директории поместим указанный ранее 000-default.conf и создадим Docker образ:

docker build -t apache_tls .

После этого запустим контейнер:

docker run --rm -t apache_tls bash -c '/etc/init.d/apache2 start;bash'

Запуск контейнера с сервером apache
Запуск контейнера с сервером apache

Теперь, когда настроили сервер apache, перейдем обратно в контейнер ssl_debug_gdb_cont. Используя утилиту curl будем отправлять запросы на адрес apache сервера, используя разные версии протоколы tls. Но перед этим нужно изменить конфигурацию openssl на стороне клиента, чтобы было доступно использование всех версий протокола, используя команду:

sed -i '1iopenssl_conf = default_conf' /usr/lib/ssl/openssl.cnf\
&& sed -i -e '$a[ default_conf ]\
ssl_conf = ssl_sect\
[ssl_sect]\
system_default = ssl_default_sect\
[ssl_default_sect]\
MinProtocol = TLSv1\
CipherString = DEFAULT:@SECLEVEL=1' /usr/lib/ssl/openssl.cnf

Для отправки запросов с разными версиями протокола, воспользуемся следующими командами:

curl --tlsv1.0 --tls-max 1.0 -k https://172.17.0.3 > /dev/null
curl --tlsv1.1 --tls-max 1.1 -k https://172.17.0.3 > /dev/null
curl --tlsv1.2 --tls-max 1.2 -k https://172.17.0.3 > /dev/null
curl --tlsv1.3 --tls-max 1.3 -k https://172.17.0.3 > /dev/null
Отправка запросов с различными версиями TLS
Отправка запросов с различными версиями TLS

Чтобы wireshark мог расшифровывать трафик нужно после начала захвата трафика  перейти в Edit->Preferences, после чего выбрать Protocols->TLS, где в (Pre)-Master-Secret log filename указать путь до файла с секретами.

Указание файла с секретами
Указание файла с секретами
Расшифрованный трафик в Wireshark
Расшифрованный трафик в Wireshark

Стоит отметить, что при использовании данного метода, можно расшифровывать не только https трафик, но и любой другой, если утилита использует openssl. В качестве примера рассмотрим msmtp, которая хоть и использует по умолчанию gnutls, но позволяет произвести сборку с openssl.

Для этого, аналогично openssl скачаем зависимости и произведем сборку:

apt update
apt-get -y build-dep msmtp
apt-get -y --build source msmtp

Дополнительно установим libssl-dev:

apt install libssl-dev

После изменим в файле msmtp-1.8.6/debian/rules строку

CONFIGURE_COMMON_OPTS := --with-libgsasl --with-tls=gnutls --with-msptpd

на

CONFIGURE_COMMON_OPTS := --with-libgsasl --with-tls=openssl --with-msptpd

И произведем сборку и установку пакета:

cd msmtp-1.8.6
dpkg-buildpackage -b
apt install ../msmtp-gnome_1.8.6-1_amd64.deb

Чтобы убедиться, что пакет собрался с openssl, можно воспользоваться командой:

ldd `which msmtp` | grep libssl

Теперь осталось настроить msmtp. В качестве примера рассмотрим отправку сообщений через Яндекс. Для этого изменим файл ~/.msmtprc:

.msmtprc
account    default
logfile    /var/log/msmtp.log
host       smtp.yandex.ru
port       587
from <username>@yandex.ru
keepbcc on
auth on
user <username>@yandex.ru
password <password>
tls on
tls_certcheck off

Стоит отметить, что пароль для приложения нужно предварительно создать

Напишем текст письма в файл email.txt:

email.txt
From: <username>@yandex.ru 
To: <another_username>@yandex.ru
Subject: Hello World 
 
Hello world

Теперь письмо можно отправить командой:

cat email.txt | msmtp -a default <another_username>@yandex.ru

Чтобы Wireshark расшифровал сообщения, необходимо добавить в настройках протокола SMTP порт 587.

Добавление порта в настройках SMTP
Добавление порта в настройках SMTP
Расшифровка SMTP-трафика в Wireshark
Расшифровка SMTP-трафика в Wireshark

2.4. Вывод

Тут мы уже рассмотрели чтение трафика через Wireshark и расшифровку при помощи ключей шифрования, то есть, через что, и как именно можно извлекать ключи: gdb, пересборка пакетов, LD_PRELOAD.

Заключение

В результате разбора нескольких способов расшифровки трафика, можем сказать, что при анализе HTTPS трафика, наиболее удобным вариантом является использование перехватывающего прокси, такого как Burp Suite, так как там более удобный интерфейс для анализа содержимого HTTP пакета. В случае, если необходимо исследовать трафик других протоколов, использующих TLS, то расшифровку нужно проводить через Wireshark. Из рассмотренных способов извлечения ключей шифрования, наиболее удобным, на наш взгляд является LD_PRELOAD, так как требует меньшей настройки.

Ссылки

Статьи про WireShark:

Статьи, связанные с расшифровкой TLS-трафика:

Статьи, про работу TLS:

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


  1. Vadziku
    14.05.2024 09:09

    Все методы "расшифровки" предназначены для случая локальных клиентов.

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


  1. mikaelkg Автор
    14.05.2024 09:09
    +1

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

    Если имеете ввиду, что можно поставить агента, который будет в роли прозрачного прокси перехватывать весь трафик, при этом логируя все запросы и ответы, то это тоже верно. Такой способ был указан в статье через Burp Suite в случае HTTPS.


  1. hpf
    14.05.2024 09:09
    +1

    Оставлю это здесь: https://ecapture.cc


    1. mikaelkg Автор
      14.05.2024 09:09
      +1

      Спасибо. Используемый инструментом метод мы в статье не рассмотрели. Постараемся в будущем дополнить статью


  1. Voevo
    14.05.2024 09:09

    Спасибо за статью, а как быть с двухсторонним TLS? Burp Suite его поддерживает?


    1. mikaelkg Автор
      14.05.2024 09:09
      +1

      Да, Burp Suite поддерживает использование клиентских сертификатов. Если хотите протестировать можно сделать следующим образом (если нет своего клиентского сертификата и сайта):

      1. Создать сертификат для сервера и клиента, данный процесс хорошо описывается здесь.

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

      3. Перевести клиентский сертификат и ключ в формат pkcs12 командой:

      openssl pkcs12 -export -in client.cert.pem -inkey client.key.pem -out client.p12 
      1. В Burp Suite зайти в Proxy->Proxy Settings. Слева найти Network->TLS. Пролистать до Client TLS certificates и добавить клиентский сертификат.

        Добавленный клиентский сертификат
        Добавленный клиентский сертификат

      После этого можно перенаправлять трафик через Burp Suite