Bollywood HungamaDisha Patani turns entrepreneur, launches clothing brand DZ with oversized and space-inspired designsPunchArgentine football fan jailed for threatening Di Maria over returnThe Jerusalem PostAI could accelerate quantum computing breakthrough, Bank of America strategist saysInquirerVelasco’s ‘ex-driver’ appears at NBI, alleges he delivered cash suitcases to DuterteZDF heuteAktuelle Pressemitteilungen des ZDFDaily MaverickHOUSEHOLD SQUEEZE: Fuel levy cut offers brief relief as food basket outpaces SA’s unchanged SRD grantХабрПоддержка нескольких реляционных СУБД в одном проекте: PostgreSQL и MSSQLUOLVolkswagen registra prejuízo trimestral de ? 5,8 bilhõesColliderChris Pratt Officially Returns to Streaming in 1 Month With New Military ThrillerThe RegisterMatrix takes sovereign messaging into the realm of the tech giantsWirtualna PolskaProdukowali "kokainę dla ubogich". Grozi im nawet 20 lat więzieniaRMF24W tych polskich gminach żyje się najgorzej. Oto wyniki rankingu
The Daily Newsstand · Free, Always
Thursday, October 29, 2026

QUICv2 больше не проходит: перепроверил работу о QUIC-цензуре в России

Translate

Весной на FOCI 2026 вышла работа Heitmann, Niere, Graf Lange, Somorovsky «On Russia’s Early Introduction of QUIC SNI Censorship». Авторы мерили в марте–апреле с трёх точек (Москва, Петербург, Новосибирск) и пришли к выводу, который сразу разошёлся по чатам:

it targets only QUICv1; the standardized update QUICv2 is not censored, facilitating potential circumvention at all our vantage points

То есть ТСПУ разбирает только первую версию QUIC, а вторую, которая отличается от первой парой констант, пропускает. Мне стало интересно, сколько живёт такая дыра. Я написал свой измеритель и 23 сентября прогнал его с пяти российских сетей и одной белорусской.

Коротко: на всех пяти российских сетях QUICv2 режется так же, как v1. Но интереснее, по-моему, другое: за один день мой инструмент трижды выдал правдоподобный и неверный результат, и один раз — ровно тот, который я хотел увидеть.

Почему цензор вообще видит SNI в QUIC

QUIC шифрует почти всё, включая хендшейк. Но первый пакет клиента, Initial, шифруется ключами, которые выводятся из Destination Connection ID — поля, лежащего в заголовке открытым текстом (RFC 9001, §5.2). Секрета нет: любой на пути берёт DCID, повторяет вывод ключей, снимает защиту заголовка, расшифровывает пакет и читает из ClientHello имя сервера. Это осознанный компромисс протокола: Initial защищён от случайной порчи и от middlebox’ов, которые хотят его менять, но не от чтения.

Вывод ключей укладывается в несколько строк на node:crypto:

// HKDF-Extract с солью версии, дальше HKDF-Expand-Label из TLS 1.3
function initialKeys(ver, dcid) {  const initialSecret = hkdfExtract(ver.salt, dcid);  const client = hkdfExpandLabel(initialSecret, 'client in', 32);  return {    key: hkdfExpandLabel(client, ver.labelKey, 16),    iv:  hkdfExpandLabel(client, ver.labelIv, 12),    hp:  hkdfExpandLabel(client, ver.labelHp, 16),  };
}

hkdfExpandLabel — это HkdfLabel из RFC 8446, §7.1 с префиксом tls13 и пустым контекстом, двадцать строк вокруг HMAC-SHA256.

QUICv2 (RFC 9369) функционально тот же протокол. Его придумали в том числе против окостенения: чтобы middlebox’ы не привыкали к одной версии. Отличий для Initial четыре:

QUICv1

QUICv2

version

0x00000001

0x6b3343cf

initial salt

38762cf7…7f0a

0dede3de…2ed9

метки HKDF

quic key / quic iv / quic hp

quicv2 key / quicv2 iv / quicv2 hp

тип пакета Initial

0b00

0b01

Если парсер цензора знает только первую колонку, вторая для него — мусор, и пакет проходит. Это и описали Heitmann и соавторы.

Как я мерил

Инструмент на Node 18+ без зависимостей: HMAC, AES-GCM, AES-ECB для защиты заголовка и UDP есть в стандартной библиотеке. Пакет собирается руками, поэтому первым делом сверка с тестовыми векторами из приложения A RFC 9001: пока ключи и защищённый заголовок не сходятся байт в байт, мерить нечего.

Хендшейк я до конца не довожу. Нужен только факт, что пакет дошёл: любой ответ сервера — Initial, Retry или Version Negotiation — это «прошёл», тишина — «дроп». Правил, без которых результат ничего не значит, набралось четыре:

  1. Адресат один на все пробы, меняется только SNI. Иначе не понять, что стало причиной: домен, маршрут или сам сервер. Я слала всё на quic.aiortc.org.

  2. Свежий исходный порт на каждую пробу. По данным работы, после срабатывания ТСПУ держит блокировку по 4-tuple ещё 420 секунд. Переиспользуешь порт — следующая проба наследует чужой бан.

  3. Повторы вперемешку. UDP теряется сам по себе. В самом первом прогоне одиночная проба discord.com не получила ответа и записалась в блокировки — на пяти повторах ответ пришёл все пять раз. В работе по той же причине делали по 10 повторов на домен.

  4. Контроль для v2. Главная ловушка: если сервер не умеет QUICv2, его молчание неотличимо от блокировки. Поэтому адресат должен отвечать и на v1, и на v2 либо хотя бы на пробу с несуществующей GREASE-версией: на неё сервер по RFC 9000 обязан ответить Version Negotiation. quic.aiortc.org отвечает на всё, 3 из 3.

Читается результат так:

v1

v2

вывод

дроп

прошёл

обход работает, как в работе

дроп

дроп

режутся обе версии, парсер обновили

прошёл

прошёл

домен на этой сети не блокируется

прошёл

дроп

почти наверняка дело в сервере, а не в цензуре

Дальше скан: 300 первых доменов из Tranco (у авторов было 100 000). Фаза 1 — быстрый триаж по одной пробе, фаза 2 — подозрительные домены по пять проб на версию плюс контрольная выборка заведомо доступных, фаза 3 — тот же домен по TLS поверх TCP.

Что получилось

Первый прогон с машины, на которой я писал инструмент, показал, что QUIC там не фильтруется вообще: четыре проверочных домена прошли 5 из 5 в обеих версиях. Контроли зелёные, векторы RFC сходятся, просто цензуры на этом канале нет. Подтвердить или опровергнуть работу такой прогон не может, поэтому дальше я мерил с российских адресов в пяти разных автономных системах, включая Ростелеком.

На всех пяти сетях 15 доменов из топ-300 режутся в обеих версиях:

bbc.com              facebook.com            medium.com
cdninstagram.com     fbcdn.net               soundcloud.com
discord.com          godaddy.com             t-online.de
doubleclick.net      googleadservices.com    t.co
linkedin.com         googlesyndication.com   twitter.com

Ещё несколько добавляются на отдельных сетях: bbc.co.uk, instagram.com, linktr.ee, pages.dev, whatsapp.com, snapchat.com, dell.com. Выходит 18–19 доменов на точку при общем ядре в 15. Провалов контрольной выборки ноль.

Итог скана по Ростелекому для примера:

проверено доменов          300
блокируется QUICv1         21
блокируется QUICv2         18
блокируется обе версии     18
только v1 (v2 проходит)    3
нестабильных               3
провалов контроля          0

Три домена, где v2 прошёл, — snapchat.com, pages.dev и bbc.co.uk. На других сетях те же домены режутся в обеих версиях. Остаток старой дыры это или шум пяти проб, по одному дню сказать не могу. Ядро в 15 доменов от этого не зависит.

Ещё одно расхождение: пара доменов режется по QUIC, а по TLS открывается, например nih.gov. В работе блоклисты QUIC и TLS совпадали, у меня совпадают почти полностью, но не до конца.

Как инструмент мне врал

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

ClientHello на два байта длиннее. Я ошибся в длине расширения key_share: записал 38 вместо 36. Серверы отвечали TLS alert вместо ServerHello. Для «дошёл или нет» это даже годится, ответ ведь пришёл, но заметил я ошибку только потому, что три независимых хоста ответили одинаковым alert’ом.

\r в списке доменов. Список Tranco пришёл с CRLF, и SNI уходил как "discord.com\r". Такого имени нет ни в одном блоклисте, поэтому три скана по 300 доменов дали ровно ноль блокировок. Вывод «QUIC в России не фильтруется» выглядел вполне правдоподобно. Спасло противоречие с прицельными пробами в тех же прогонах: там discord.com резался стабильно.

Порядок проб. Самый неприятный случай. Два прогона показали 72 и 77 доменов, где v1 режется, а v2 проходит, — то есть ровно обход из работы, только в масштабе, о котором авторы и не писали. В этих списках оказались google.com, github.com, cloudflare.com, apache.org, apple.com, gstatic.com.

Причина нашлась в фазе 2: для каждого домена я сначала слал пять проб v1, потом пять проб v2. Любой эффект, который бьёт по первой серии пакетов, — всплеск потерь, рейт-лимит на канале или у адресата — выглядит при таком порядке как «v1 заблокирован, v2 работает». Инструмент систематически производил ту самую картину, которую искал. Косвенный признак был в цифрах сразу: нестабильных доменов в этих прогонах 24 и 41, в остальных трёх — от 3 до 7. Артефакт вылез ровно на шумных каналах.

Исправил двумя способами. Пробы v1 и v2 стали отдельными задачами в общем перемешанном списке, так что версия больше не связана с моментом отправки. И появились «канарейки»: google.com, cloudflare.com, apache.org, microsoft.com, gstatic.com, akamai.net, amazonaws.com, wikipedia.org. Блокировка любой из них объявляет весь прогон недействительным. Контрольная выборка этого не ловила: она набирается из доменов, прошедших фазу 1, а значит заранее смещена в сторону исправных.

Важно, что этот дефект не затрагивает главный вывод. Он умеет выдумать «v1 режется, v2 проходит», но не «режутся обе»: вторая серия проб в таком случае восстановилась бы.

Системный DNS. Это уже TLS-проба. Она брала адрес у резолвера провайдера, а тот для части доменов отдавал заглушку: четыре несвязанных домена на одном адресе. TLS с заглушкой успешно поднимался, и проба записывала домен в рабочие. Теперь домен резолвится и системно, и через DoH, при расхождении проверяется адрес от DoH, а отказ системного резолвера при живом DoH записывается как блокировка DNS.

Общий урок у всех четырёх один. Ловились они только сопоставлением независимых источников: прицельной пробы со сканом, QUIC с TLS, вывода со здравым смыслом о том, что google.com не может быть заблокирован. Если проба не проверяет, до того ли сервера дошла и то ли имя отправила, она измеряет не то, что вы думаете.

TLS: молчание против сброса

Заодно видно, как по-разному устроены фильтры. В России TLS-соединение с запрещённым SNI ведёт себя так: TCP поднимается, ClientHello уходит, а ответа нет. Через 6 секунд срабатывает таймаут. Контрольная проба с SNI=example.com на тот же IP получает ответ за 320–380 мс. Это молчаливый дроп, устройство стоит в разрыве (in-path).

С белорусской сети (A1, AS42772) я проверил пять заблокированных там новостных сайтов. После ClientHello через 20–30 мс прилетает RST, контроль с example.com проходит за то же время. Мгновенный ответ — признак инъекции с устройства на ответвлении (on-path), такого устройства, которое само пакет не держит, а только видит его копию. По устройству это ближе к китайскому GFW, чем к ТСПУ. Разница во времени отклика в два порядка — прямое следствие архитектуры: инъектор отвечает сразу, устройство в разрыве просто молчит.

QUIC по SNI в той белорусской сети не фильтровался вовсе: 80 проб из 80 прошли, ни v1, ни v2 не тронуты. Работа Heitmann и соавторов заканчивается призывом посмотреть на незамеченную QUIC-цензуру в других странах. Про Беларусь я в литературе ничего не нашел, так что пусть будет хотя бы одна точка.

Ограничения

  • Российские точки — адреса в датацентрах, не домашний интернет. Фильтрация на резидентских линиях может отличаться.

  • 300 доменов против 100 000 у авторов.

  • Один день, без повторов во времени.

  • Только IPv4.

  • QUIC-пробы шли на сторонний адресат, а не на серверы самих доменов. Строго измерено только «Initial с таким SNI не доходит по этому пути».

  • Два прогона из пяти забракованы, повтор на тех же сетях с исправленным порядком я пока не делала.

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

Источники

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.