В данной статье я хочу немного поговорить о такой старой и знакомой заядлым пентестерам теме как Domain fronting. Тем более, что после недавних нововведений от компании Cloudflare эта избитая тема заиграла новыми красками.
Краткое содержание предыдущих серий
Давным-давно, во времена, когда «зловещие русские хакеры» были ещё совсем маленькими, а такое модное сейчас слово как APT (advanced persistent threat) не звучало из каждого утюга пентестерского телеграм-канала, у хакеров и сетевых профи существовала возможность прятаться от систем слежения, антивирусов и фильтрующих прокси (EDR/XDR тогда ещё и в помине не было) с помощью специальной техники прикрытия известными доменными именами, пользующимися доверием у пользователей и средств защиты. Если кратко, то можно представить эту схему так: мы коннектимся к серверам, например, яндекса по проколу https, а уже внутри http-протокола пишем Host: xaker.com. Фронт яндекса видит этот заголовок и переадресует наше подключение уже на сервер xaker.com
Таким образом, для антивируса или прокси-сервера, который запрещает нам напрямую подключаться к xaker.сom, это подключение будет вполне себе легитимным. В качестве фронт-доменов выбирались CDN-сервисы крупных провайдеров: Майкрософта, Амазона, Яндекса, Cloudlare.
Когда данная фишка сокрытия от зорких глаз товарища майора пошла в народ, и её подхватили различные малварщики, CDN и облачные провайдеры поняли, что с этим надо как-то бороться. Они стали выключать данную возможность в своих облаках для всех, кроме аккаунтов и доменов с корпоративной подпиской, то есть лиц, пользующихся определенным доверием.
В 2018-м году компания Cloudflare придумала и первой внедрила новую фичу в протокол TLS1.3 ESNI (encrypted server name identifier). Это когда поле SNI (server name identifier) в составе пакета client-hello отсылалось на сервер в зашифрованном виде, а шифровалось оно с помощью публичного ключа, который надо было предварительно запросить у сервера через DNS TXT запись домена. Т.е опять же, если кратко: сначала мы запрашиваем TXT запись _esni.xaker.com, получаем публичный ключ, потом этим ключом шифруем поле SNI в ssl пакете client-hello, и отправляем его на сервер. Как мы всею, надеюсь, знаем из школьных уроков криптографии, при таком подходе никто, кроме владельца закрытого ключа, т.е. серверов Cloudflare не мог расшифровать это поле и понять, кому же предназначены данные соединения.
Дополнительной фишкой данного соединения являлось то, что помимо обязательной зашифрованной части ESNI пакета client-hello мы могли снабдить его и стандартной незашифрованной частью SNI. И вот в нём уже указать абсолютно любой домен, которым мы хотим прикрыться. Мы подробно описывали эту технологию в 2019-м году в статьях на Хабре, показывая на примерах как подключиться к рутрекеру под прикрытием домена kremlin.ru. Прочтите — вполне доходчиво и интересно.
https://habr.com/ru/post/475372/ - первая часть
https://habr.com/ru/post/477696/ - вторая часть
Когда эта TLS фишка опять же пошла в народ была взята на вооружение малварщиками, инженеры из Cloudflare сказали «Ой» спешно её закрыли (правда, как и в прошлый раз, оставив её работать для своих любимых клиентов с корпоративной подпиской). С 2020-го года прикрыться чужим доменом уже не представлялось возможным, но механизм с ESNI по-прежнему работал. То есть в составе SSL client-hello пакета уже не было открытой части server name, а была лишь только закрытая.
И вот, то ли весной, то ли летом 2022 года Cloudflare полностью убрала поддержку ESNI со своих серверов. Сделали они это не потому, что этим механизмом начали пользоваться все малварщики мира, а потому что на смену ESNI пришёл ECH (Encrypted Client Hello). О принципах работы данного механизма инженеры Cloudflare написали несколько статей. Там много нововведений, но если вкратце, то суть примерно такая же – сначала мы запрашиваем публичный ключ для домена, а затем шифруем этим ключом теперь уже весь client-hello пакет, включая и server name поле.
Подробнее про переход с ESNI на ECH на русском
Переходим к водным процедурам (практическим занятиям)
Прежде всего, давайте проверим в браузере как работает ECH. Берём последний Google Chrome (на момент написания статьи — 108й) и проделываем с ним пару нехитрых конфигураций:
Включаем SecureDNS и выбираем провайдера
Через настройки включаем сам ECH
На этом конфигурирование Хрома завершено. Для проверки можно открыть специальную страницу проекта defo.io (комьюнити, работающее над внедрением ECH) — https://defo.ie/ech-check.php
Заветная зелёная галочка сообщает нам, что соединение с сервером произошло с использованием ECH. Мы обращались к домену defo.ie, именно это значение было в зашифрованной части ClientHello, а в качестве «прикрытия» использовали домен cover.defo.ie, что можно видеть, если посмотреть трафик в wireshark’е.
Так же проверить работу можно и на сайте Клаудфлары.
В качестве другого практического занятия попробуем собрать openssl и curl с поддержкой ECH. Благо, ребята из проекта DEFO уже позаботились о нас и подробно описали как это сделать.
Просто следуем инструкциям, качаем с гитхаба соответсвующие сорцы и собираем у себя.
Новый curl имеет соответствующий параметр --echpublic, который как раз и инструктирует нашу сборку, относительно домена прикрытия. Наш пример с курлом из прошлых статей будет выглядеть так:
В данном случае сначала необходимо получить от DNS-сервера ECH-ключи (так называемый ECHConfig). Это можно сделать запросив соответствующую запись с DNS-сервера, обслуживающего интересующий нас домен. Необходимые нам ключи содержатся в ресурсной DNS-записи HTTPS (или TYPE65) и получить их можно, например, с помощью утилиты dig:
Получить ECHConfig необходимо непосредственно перед выполнением запроса, потому что, как и в случае с ESNI, ключи меняются примерно раз в полчаса.
Тут так же следует отметить, что для своих клиентов (доменов) сервера клаудфлары не генерируют отдельные ECHConfigs и не отдают их при соответствующих запросах, но ECHConfig для домена crypto.cloudlare.com будет работать для любых доменов, заведенных за Cloudflare. Видимо, даже в самой клаудфларе процесс внедрения ECH до сих пор не «устаканен» до конца.
Поближе к хакерству
Итак, мы посмотрели вблизи на новый механизм ECH и как его можно применить на практике. Теперь давайте адаптируем какой-нибудь хакерский инструмент (curl не в счет) под эту новую технологию. В пошлый раз мы это делали с Rsocks-туннелером. Теперь же проведём данную операцию с другим похожим инструментом (также реализованным на GO), а именно - туннелером Ligolo-ng от французского чатланина разработчика (https://github.com/nicocha30/ligolo-ng).
Изначально ligolo-ng не умеет работать с websocket. А поддержка данного протокола необходима для работы через Cloudflare. Добавление websocket в ligolo-ng выходит за рамки данной статьи. Скажу лишь, что ничего особо нового тут не требуется, и для websocket я использовал те же стандартные подходы/библиотеки, что и для Rsocks.
Для того чтобы научить ligolo-ng работать с ECH необходимо использовать соответствующий форк GO от Cloudflare, обеспечивающий поддержку ECH (на момент написания статьи это GO 1.18.3 ) - https://github.com/cloudflare/go. Качаем его с гитхаба, собираем у себя и используем в качестве GOROOT при компиляции ligolo-ng.
После компиляции у нас получаются два исполняемых модуля: proxy – серверная часть и agent – клиентская часть туннелера.
Сервер запускаем как обычно, с указанием нужного порта и префикса https, который сообщит туннелеру, что работать нужно по протоколу websocket:
Агента также запускаем с указанием флага -ech, префикса https, и нашего домена, заведенного за клаудфлару:
Видим, что агент удачно зачекинился на сервере. Дальше всё стандартно. Стартуем туннелинг и работаем как через VPN с пингами и UDP-протоколом. Чудеса, да и только!
Что же при этом видит Роскомнадзор wireshark:
Он видит, что сначала был отправлен запрос на dns.google.com (именно через DoH агент запрашивает ключи ECHConfigs), а затем пошёл запрос на IP-адрес серверов клаудфлары, но никаких имен серверов в этих пакетах нет.
В принципе, уже неплохо. Промежуточный узел видит только IP-адреса, но не домены назначения. Однако, нам этого мало. Необходимо провести операцию прикрытия, чтобы ни у кого не возникало даже мысли о блокировке нашего соединения. Поэтому мы делаем «ход конем». Открываем файл src/crypto/tls/ech.go из GO-форка, находим строчку
hello.serverName = hostnameInSNI(string(echConfig.rawPublicName))
и меняем её на что-то более благозвучное настоящему комсомольцу ИБ-ресёрчеру:
Пересобираем агента, запускаем и смотрим, что на сей раз видят ребята из Роскомнадзора.
А они видят, что теперь самый главный домен страны надежно защищен лучшим в мире CDN, немного удивляются конечно, но одновременно успокаивают себя тем фактом, что теперь "хоть что-то у нас в безопасности".
Заключение
В данной статье мы познакомились с новой технологией шифрованния ClientHello-пакетов протокола TLS1.3 и увидели, что данную технологию можно применять как с точки зрения defensive (защита от слежки со стороны РКН и ему подобных), так и с точки зрения offensive (DomainFronting). Пока что форк Ligolo-NG с возможностями websocket и ECH размещен на гитхабе автора статьи в отельной ветке (https://github.com/virusvfv/ligolo-ng/tree/websocket), но сделан соответсвующий pull-request и вполне возможно, что он войдет в основную ветку проекта.
Вангую, что довольно-таки скоро, когда уже и эта фишка с ECH DomainFronting «уйдет в народ», ребята из клаудфлары опять скажут «Ой», и отключат возможность прикрытия произвольным сleartext SNI (outer server name), как это уже было с ESNI. Но всё-таки технология ECH не стоит на месте, стандарт TLS 1.3, как я полагаю, довольно скоро станет уже настоящим стандартом, его начнут поддерживать остальные CDN (Google, Amazon, Microsoft), а браузеры начнут работать по ECH уже из коробки. Что тогда будет делать РКН - увидим.
Но это будет потом. Впереди зима...