XTLS-Reality: как протокол маскирует VPN и обходит DPI

Автор: Редактор блога HubHide VPN

В разборе VLESS я описал Reality в двух абзацах: подставляет чужой сертификат, наблюдатель видит обычный сайт, всё работает. Для обзора достаточно. Для понимания — нет, и я это понял по письмам после той статьи.

Главный вопрос там остался без ответа. Ладно, сертификат чужой. Но откуда сервер его берёт, что происходит, когда по вашему адресу стучится не клиент, а проверяющий, и почему нельзя взять любой популярный домен, вписать его в конфиг и жить счастливо?

Разберу механику подробно: будет технически, местами занудно, с матчастью про TLS. Инструкций по развёртыванию не будет. И признание на входе: часть этой механики я понял не с первого раза, а с третьего, причём момент с key_share дошёл до меня, только когда я читал чужой конфиг и вдруг сообразил, зачем там нужен огрызок из восьми символов. Так что если покажется, что я разжёвываю очевидное, — это я себе.

Почему обычный VPN по TLS всё равно отличается от браузера

Логика «завернём туннель в HTTPS, и его не отличат от сайта» выглядит железно: порт 443, шифрование, сертификат. На этой идее построены десятки решений, и все они по очереди перестали работать.

Причина в том, что TLS — это не только шифрование, но ещё и подробный разговор двух сторон перед тем, как шифрование начнётся. И разговор этот виден целиком. Первое сообщение называется ClientHello и идёт открытым текстом, а внутри на удивление много: какие версии протокола клиент поддерживает, какой список шифронаборов предлагает и, что важнее, в каком порядке, какие кривые умеет, какие расширения включены и в каком порядке идут. Плюс SNI — имя домена, к которому клиент направляется. И вот тут начинается интересное, потому что разные библиотеки составляют этот пакет по-разному. Chrome предлагает один набор шифров в одном порядке, Firefox другой, Safari третий. Стандартная библиотека Go, на которой написана половина сетевого софта, — четвёртый, и он не похож ни на один браузер ни капли.

Это и есть TLS-фингерпринт: не одно поле, а совокупность, слепок того, как клиент составил своё первое сообщение. Сравнивать такие слепки научились давно, у методов есть устоявшиеся имена вроде JA3 и JA4.

Аналогия банальная, но точнее я не придумал: почерк. Текст один, буквы те же, а рука разная.

[Изображение: разбор пакета ClientHello по полям — версия, шифронаборы, расширения, SNI — с подсветкой тех, что формируют фингерпринт]

Вот на этом старые решения и сыпались. Соединение идёт на 443, выглядит как HTTPS, а фингерпринт не совпадает ни с одним браузером на рынке — людей, которые ходят на сайты через самописный клиент на Go, примерно ноль. Значит, не браузер. Значит, туннель. Добавьте второй слой признаков: TLS внутри TLS, где браузер уже зашифровал запрос, а туннель шифрует его ещё раз, и у такой матрёшки характерные размеры пакетов и тайминги. Этим занимается XTLS-Vision. Но фингерпринт рукопожатия им не закрывается, держите это в голове.

TLS-фингерпринт и активное зондирование: две разные проверки

Важно развести два механизма, которые постоянно валят в кучу, — я сам их путал полгода, пока не нарисовал на бумажке.

Пассивный анализ. Система стоит на пути трафика и смотрит: сравнивает фингерпринт с профилями браузеров, оценивает размеры пакетов, ритм обмена, длительность сессии. Ничего не делает — наблюдает и копит подозрения. Молча.

Активное зондирование. Инструмент принципиально другой: заподозрив соединение, система сама идёт по тому же адресу и порту, изображает клиента, отправляет своё рукопожатие, иногда повторяет ваше — и смотрит, что ответит сервер. Это как тайный покупатель: вывеску можно повесить любую, но придёт человек, попросит товар, и станет понятно, магазин это или картонный фасад. Классическая связка «домен плюс сертификат» на этой проверке и ломается. Проверяющий приходит по адресу вашего сервера, а там либо тишина по HTTP, либо пустая заглушка, либо — самое смешное, я такое видел дважды — страница панели управления. Настоящих сайтов без содержимого не бывает. Обходить пытались по-разному: заглушка рядом, fallback на локальный nginx, и помогает это, скажем так, частично. Однодневка с тремя страницами лорем ипсума и нулевой посещаемостью — такой же демаскирующий признак, просто менее явный.

Идея Reality: не изображать сайт, а пользоваться настоящим

Ход мысли авторов, насколько я его понимаю, был такой: если подделка всегда хуже оригинала — давайте не подделывать. Впрочем, давайте по шагам.

Шаг первый. Клиент формирует ClientHello не своим почерком, а точной копией почерка настоящего браузера: за это отвечает библиотека uTLS, воспроизводящая профили Chrome, Firefox и Safari вплоть до порядка расширений и GREASE-значений. В конфиге это параметр fp. Фингерпринт получается не похожий на браузерный, а буквально браузерный — различие, которое я недооценивал.

Второй. В поле SNI клиент пишет не адрес вашего сервера, а домен стороннего сайта-прикрытия, и для наблюдателя соединение выглядит как обращение к этому сайту.

Третий — самый красивый, и именно он до меня долго не доходил. В ClientHello есть расширение key_share, где клиент передаёт открытый ключ X25519. Reality использует его как контейнер: внутрь кладётся метка, вычисленная из открытого ключа сервера (тот самый pbk в конфиге) и короткого идентификатора sid. Снаружи это просто случайные байты, каких в любом рукопожатии полно. Никаких лишних полей и пакетов не появляется — и когда я это осознал, стало обидно за потраченные вечера.

И наконец, четвёртый. Сервер проверяет метку своим закрытым ключом. Дальше развилка, и она решает вообще всё.

[Изображение: схема рукопожатия Reality — две ветки развилки: свой клиент и зонд, с указанием, что происходит в каждом случае]

Если метка верна — это свой. Сервер идёт к сайту-прикрытию, забирает настоящую цепочку сертификатов и отдаёт клиенту, но подпись в CertificateVerify подменяет своим ключом. Клиент, у которого в конфиге лежит pbk, эту подпись проверяет: совпало — на том конце действительно наш сервер; не совпало — клиент рвёт соединение и ведёт себя как браузер, которому не понравился сертификат.

Если метки нет или она неверна — а это ровно то, что произойдёт при зондировании или при случайном заходе браузером, — сервер прозрачно проксирует соединение на сайт-прикрытие, целиком, вместе с рукопожатием. Проверяющий получает настоящий сайт с валидным сертификатом, потому что это и есть настоящий сайт. Он просто съездил туда через ваш сервер, не зная об этом.

Отсюда, кстати, и устойчивость. Проверять нечего: по адресу действительно живёт то, что там должно быть. Аналогия, которая мне кажется рабочей, — магазин с честной витриной и настоящим товаром: зашёл прохожий или проверяющий — он в магазине, ему пробьют чек. А для своих есть служебная дверь, которая открывается только на определённый стук, и стоит она прямо в общем зале, от стены не отличить. Аналогия хромает, второго помещения физически нет. Но развилку передаёт.

Как выбирается сайт-прикрытие и почему от этого зависит всё

Самая недооценённая часть, про которую в гайдах пишут строчкой: «укажите любой популярный домен». Совет плохой, а требований, если честно, изрядно.

Технические. Сайт должен поддерживать TLS 1.3 — без него схема не работает — и уметь X25519. Желательно HTTP/2. И не должен требовать клиентских сертификатов.

География. Домен должен сочетаться с адресом вашего сервера: сервер в Нидерландах и прикрытие, которое обслуживается где-нибудь в Австралии, — картина странная, имя ведёт в одну сторону, адрес в другую.

Популярность. Сайт должен быть таким, куда люди реально ходят: соединение к домену, о котором в вашей стране не слышал никто, само по себе аномалия — тем более если таких соединений с одного адреса идёт много и подолгу.

Незаблокированность. Очевидно, но забывают. Попадёт домен-прикрытие в блокировку — туннель ляжет вместе с ним, хотя с сервером всё в порядке.

Стабильность. Ресурс не должен переезжать между хостерами.

И отдельный пункт, про который почти не пишут, а меня он в своё время удивил больше всего. Не берите домен из первого попавшегося гайда на GitHub: его оттуда взяли ещё несколько тысяч человек, и одно имя всплывает в SNI у сотен серверов — статистическая аномалия, которую видно без анализа содержимого. По-хорошему прикрытие подбирается под локацию и периодически пересматривается: это часть эксплуатации, а не разовая настройка. Для одного сервера вручную терпимо. Для инфраструктуры — рутина, которую держит мониторинг.

Что в итоге видит DPI

Картина со стороны наблюдателя выходит скучная — вернее, никакая, и в этом вся суть. Он видит соединение на порт 443 к непримечательному адресу, ClientHello с фингерпринтом браузера, SNI известного домена и зашифрованный поток. Он не видит признаков туннеля в рукопожатии, не видит сигнатуры TLS-в-TLS, за неё отвечает Vision, не видит лишних полей и «шума». А решит проверить активно — получит работающий сайт.

Протокол Что видно системе анализа Результат проверки зондом
OpenVPN Характерный заголовок, опознаётся уверенно Отвечает не как веб-сервер
WireGuard UDP с узнаваемой структурой первых пакетов Молчит или отвечает нехарактерно
Shadowsocks Поток без структуры, подозрителен самой бесформенностью Молчит на любой запрос
VMess/VLESS поверх обычного TLS Фингерпринт не совпадает с браузерным Пустая заглушка или панель
VLESS + Reality Браузерный фингерпринт, SNI живого домена Настоящий работающий сайт

[Изображение: сравнительная схема «что видит DPI» для OpenVPN, WireGuard, Shadowsocks и Reality]

Reality против обфускации и Shadowsocks

Разница тут, кстати, концептуальная, а все три подхода вечно валят в одну кучу. Shadowsocks в базовом виде шёл по пути «сделать трафик бесформенным»: никаких заголовков, никакой структуры, просто поток байтов. Идея работала ровно до тех пор, пока анализаторы искали известные сигнатуры, а потом научились смотреть на энтропию — и выяснилось: поток без структуры в интернете сам по себе редкость. Отсутствие признаков стало признаком. Мне эта мысль показалась почти издевательской, но она верная.

Обфускация — следующая попытка: добавим шума, набросаем сверху что-то похожее на легитимный протокол. Работает лучше, но упирается в ту же стену: маскировку писал человек, представлявший себе легитимный трафик, а представление всегда приблизительное. Где-то поле не то, где-то порядок, где-то тайминги.

Reality ничего не изображает и не добавляет: рукопожатие настоящее, фингерпринт браузерный, сертификат чужой настоящий, сайт за ним настоящий. Разница между «очень похоже» и «то же самое» качественная, и если унесёте из статьи одну мысль, пусть это будет она.

Где Reality может не сработать

Ладно, теперь честная часть. Идеальных решений не бывает, и вечную защиту я вам продавать не собираюсь: за десять лет я не встречал ни одной.

Белые списки с привязкой к адресам. Фильтрацию по именам доменов Reality проходит отлично: SNI разрешённый, пропускаем. Но если система сверяет ещё и то, ведёт ли имя на официальные адреса владельца, схема разваливается: ваш сервер стоит не в подсетях того сайта, чьё имя в SNI. Насколько массово такая сверка применяется — вопрос открытый, и утверждать я не берусь.

Статистика поведения. Рукопожатие безупречно, а вот что происходит после — уже нет. Соединение к новостному сайту, которое живёт шесть часов и перекачивает десятки гигабайт, выглядит странно независимо от того, как начиналось.

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

Устаревшие профили. Браузеры обновляются, рукопожатия меняются, и клиент, изображающий Chrome полуторагодовой давности, выделяется не тем, что не похож на браузер, а тем, что похож на музейный экспонат.

Неудачное прикрытие. Всё, что написано выше про выбор домена. Плохой сводит на нет всю конструкцию.

Будет ли Reality работать через год? Не знаю. Осенью 2026-го работает, и лучше остального. Но методы анализа развиваются, и когда-нибудь появится следующая итерация — так было со всеми решениями до него.

Как убедиться, что у вас действительно Reality

Три уровня проверки, от ленивого к полезному.

Смотрим конфиг. Откройте профиль в своём клиенте. В строке подключения должно быть security=reality, рядом pbk (открытый ключ сервера), sid, fp (профиль фингерпринта, обычно chrome) и sni с доменом прикрытия. Плюс flow=xtls-rprx-vision — это про Vision, но в связке они идут почти всегда. Если вместо reality стоит tls, а в конфиге ваш собственный домен, у вас классическая схема: рабочая, но живёт хуже.

Проверяем соединение. Подключились и прогнали проверку: адрес сменился, DNS не утекает, WebRTC не показывает реальный адрес. Последние два пункта к Reality отношения не имеют, но обнуляют весь смысл, если не в порядке.

[Изображение: страница проверки соединения с подсвеченными полями — IP, DNS, WebRTC]

Проверяем устойчивость на практике. Единственная проверка, которая по-настоящему имеет значение, и синтетические тесты её не заменят. Посидите несколько дней в разных условиях: дома, на мобильном, в чужом Wi-Fi. Обязательно в вечерний час пик. Посмотрите, что происходит при переключении между сетями и после того, как телефон часок полежал в кармане. Вот тут и вылезает разница между «в лаборатории работает» и «работает у меня» — по моему опыту, на третий-четвёртый день, а не в первые десять минут. Проверять всё это надо до того, как заплатили: обход блокировок у каждого оператора в России выглядит немного по-своему.

Если сводить к одному абзацу

Reality решает не задачу шифрования — её давно решил TLS. Он решает задачу неотличимости: чтобы и пассивный анализ, и активная проверка видели обычное посещение обычного сайта. Не похожее на него, а именно его — с настоящим сертификатом, фингерпринтом и ответом на том конце. Всё остальное — детали.

Проверять такие вещи стоит руками. HubHide работает на VLESS и Reality, тест 14 дней бесплатно и без карты — смотрите на своём операторе, в своё вечернее время, на своих устройствах. Не подойдёт — ничего не потеряли. А заодно загляните в конфиг: теперь вы знаете, что там должно быть.

Частые вопросы

Что такое Reality в VPN?

Это режим работы TLS в Xray, при котором сервер не использует свой домен и сертификат, а подставляет настоящую цепочку сертификатов стороннего популярного сайта. Своим клиентам он отдаёт туннель, остальным прозрачно проксирует запрос на этот самый сайт. Обычно применяется в связке с VLESS и XTLS-Vision.

Чем Reality отличается от обфускации?

Обфускация добавляет к трафику маскирующий слой — шум или имитацию другого протокола, и качество её ограничено тем, насколько точно автор представлял легитимный трафик. Reality ничего не добавляет и не имитирует: рукопожатие настоящее, фингерпринт браузерный, сертификат реального сайта. Это разница между похожей копией и оригиналом.

Можно ли обнаружить Reality?

Само рукопожатие надёжно не отличается от браузерного, и зондирование его не вскрывает. Уязвимые места в другой плоскости: статистика поведения соединения, несоответствие домена в SNI и адресов владельца при строгих проверках, плюс блокировка подсетей хостера, против которой маскировка бессильна.

Почему Reality работает при белых списках?

Когда фильтрация пропускает только разрешённые домены и опирается на SNI, Reality проходит: в SNI стоит имя легитимного разрешённого сайта. Если же система дополнительно сверяет, ведёт ли имя на адреса настоящего владельца, схема перестаёт работать. Универсальной гарантии здесь нет, и любой, кто её даёт, лукавит.

Вам будет интересно