Ограничение HTTP/3 (QUIC)

Единственная проблема с атакой fake против QUIC, которую я нашел , это спорадическое застревание потока на середине, как описано выше. Видимо оно вызванно какими-то статистическими методами анализа на ТСПУ, срабатывающими через раз.

Но если этого не случается, то поведение броузеров на провайдерах с ТСПУ + nfqws и провайдерах без ТСПУ и без nfqws выглядит идентичным

Броузеры сейчас слишком умные. Их цель - не подключить вас по самому крутому и модному протоколу, а загрузить страницу как можно быстрее. Исходя из этих соображений они сами вольны выбирать какое подключение использовать и через какую версию ip.

У меня и на хромиуме, и на мозилле, quic.nginx.org проходит тест на QUIC, а cloudflare не проходит на обоих броузерах. Смотрим F12 и видим, что там первая часть запросов идет по http/2, вторая по http/3. Если убрать nfqws на провайдере с ТСПУ, то все запросы идут http/2

Мне сообщали, что на некоторых провайдерах (yota), они стали дешифровывать QUIC на DPI. Я не в курсе подробностей, но дело вот в чем. Сейчас у них задача - полностью забанить сам протокол QUIC, кроме white list ip (вконтакт, слишком серьезный клиент).
Если они научатся анализировать QUIC, они могут перестать банить сам протокол и убрать статистический анализ.

Собрал curl с quiche.
2 теста на провайдере с ТСПУ.
первый с nfqws, второй без

curll -4Iv --http3 https://cloudflare-quic.com
*   Trying 172.67.9.235:443...
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* Connect socket 5 over QUIC to 172.67.9.235:443
* Sent QUIC client Initial, ALPN: h3,h3-29,h3-28,h3-27
*  subjectAltName: host "cloudflare-quic.com" matched cert's "cloudflare-quic.com"
* Verified certificate just fine
* Connected to cloudflare-quic.com () port 443 (#0)
* h2h3 [:method: HEAD]
* h2h3 [:path: /]
* h2h3 [:scheme: https]
* h2h3 [:authority: cloudflare-quic.com]
* h2h3 [user-agent: curl/7.83.0-DEV]
* h2h3 [accept: */*]
* Using HTTP/3 Stream ID: 0 (easy handle 0x7fffd6627210)
> HEAD / HTTP/3
> Host: cloudflare-quic.com
> user-agent: curl/7.83.0-DEV
> accept: */*
>
< HTTP/3 200
HTTP/3 200
< date: Sat, 26 Mar 2022 09:32:25 GMT
date: Sat, 26 Mar 2022 09:32:25 GMT
< content-type: text/html
content-type: text/html
< content-length: 109425
content-length: 109425
< expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< server: cloudflare
server: cloudflare
< cf-ray: 6f1ee103ee899150-FRA
cf-ray: 6f1ee103ee899150-FRA
< alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
* Connection #0 to host cloudflare-quic.com left intact
./curll -4Iv --max-time 5 --http3 https://cloudflare-quic.com
*   Trying 104.22.9.38:443...
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* Connect socket 5 over QUIC to 104.22.9.38:443
* Sent QUIC client Initial, ALPN: h3,h3-29,h3-28,h3-27
* After 2499ms connect time, move on!
* connect to 104.22.9.38 port 443 failed: No error
*   Trying 104.22.8.38:443...
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* Connect socket 6 over QUIC to 104.22.8.38:443
* Sent QUIC client Initial, ALPN: h3,h3-29,h3-28,h3-27
* After 1249ms connect time, move on!
* connect to 104.22.8.38 port 443 failed: No error
*   Trying 172.67.9.235:443...
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* Connect socket 5 over QUIC to 172.67.9.235:443
* Sent QUIC client Initial, ALPN: h3,h3-29,h3-28,h3-27
* After 624ms connect time, move on!
* connect to 172.67.9.235 port 443 failed: No error
* Failed to connect to cloudflare-quic.com port 443 after 4374 ms: Couldn't connect to server
* Closing connection 0
curl: (28) Failed to connect to cloudflare-quic.com port 443 after 4374 ms: Couldn't connect to server

вывод nfqws --debug

packet: id=1 len=1228
IP4: 192.168.4.2 => 172.67.9.235 proto=udp sport=51710 dport=443
UDP: C6 00 00 00 01 10 71 F8 72 8E CC 61 C9 FD BB AB FA 94 E7 C1 A6 F1 14 90 55 4D 8B D1 44 3D E1 C9 ... : ......q.r..a............UM..D=.. ...
packet contains QUIC initial
hostname: cloudflare-quic.com
dpi desync src=192.168.4.2:51710 dst=172.67.9.235:443
sending fake request : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... : ................................ ...
reinjecting original packet. len=1228 len_payload=1200
packet: id=1 drop

А можно еще собрать curl с openssl-quic от мелкомягких + nghttp3 + ngtcp2. Это будет альтернативная реализация, она используется в хромиуме. Только, это непросто :slight_smile:

Есть еще одна мысль. Недавно такая проблема вскрылась.
Если в linux настроен сложный policy routing. Несколько аплинков, таблиц маршрутизации, ip rule , основанные на source address, то пакеты, высланные через raw socket-ы могут уходить не с того интерфейса. Что от nfqws, что от nping. При этом обычные пакеты уходят нормально.
Если это ваш случай, то надо убедиться, что пакеты идут действительно в нужный интерфейс и правильно заменяется source адрес , если используется NAT.
Пока что самое эффективное лечение этой ситуации - вносить ip rule, базированные на fwmark, который выставляет nfqws

Skynet, СПб
Блочится http3 до инстаграмма и фейсбука, но до гугла пропускает. Похоже стали расшифровывать заголовки.
Причем по http2 пропускает тоже. К слову у skynet нет dpi, блок идет на транзитном провайдере, скорее всего МТС

http3 до инсты

./curl -4Iv -m 5 --http3 https://instagram.com
*   Trying 31.13.72.174:443...
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* Connect socket 5 over QUIC to 31.13.72.174:443
* Sent QUIC client Initial, ALPN: h3,h3-29,h3-28,h3-27
* After 2493ms connect time, move on!
* connect to 31.13.72.174 port 443 failed: No error
* Failed to connect to instagram.com port 443 after 2507 ms: Couldn't connect to server
* Closing connection 0
curl: (28) Failed to connect to instagram.com port 443 after 2507 ms: Couldn't connect to server

http3 до инсты через vpn

./curl -4Iv -m 5 --http3 https://instagram.com
*   Trying 31.13.72.174:443...
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* Connect socket 5 over QUIC to 31.13.72.174:443
* Sent QUIC client Initial, ALPN: h3,h3-29,h3-28,h3-27
*  subjectAltName: host "instagram.com" matched cert's "instagram.com"
* Verified certificate just fine
* Connected to instagram.com () port 443 (#0)
* h2h3 [:method: HEAD]
* h2h3 [:path: /]
* h2h3 [:scheme: https]
* h2h3 [:authority: instagram.com]
* h2h3 [user-agent: curl/7.83.0-DEV]
* h2h3 [accept: */*]
* Using HTTP/3 Stream ID: 0 (easy handle 0x560fda7c27d0)
> HEAD / HTTP/3
> Host: instagram.com
> user-agent: curl/7.83.0-DEV
> accept: */*
>
< HTTP/3 405
HTTP/3 405

http2 до инсты

./curl -4Iv -m 5 --http2 https://instagram.com
*   Trying 31.13.72.174:443...
* Connected to instagram.com (31.13.72.174) port 443 (#0)
* ALPN: offers http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN: server accepted http/1.1

http3 до гугла

./curl -4Iv -m 5 --http3 https://google.com
*   Trying 64.233.162.100:443...
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* Connect socket 5 over QUIC to 64.233.162.100:443
* Sent QUIC client Initial, ALPN: h3,h3-29,h3-28,h3-27
*  subjectAltName: host "google.com" matched cert's "google.com"
* Verified certificate just fine
* Connected to google.com () port 443 (#0)
* h2h3 [:method: HEAD]
* h2h3 [:path: /]
* h2h3 [:scheme: https]
* h2h3 [:authority: google.com]
* h2h3 [user-agent: curl/7.83.0-DEV]
* h2h3 [accept: */*]
* Using HTTP/3 Stream ID: 0 (easy handle 0x55ae3ae897d0)
> HEAD / HTTP/3
> Host: google.com
> user-agent: curl/7.83.0-DEV
> accept: */*
>
< HTTP/3 301
HTTP/3 301

Они писали на форуме, что на разных тарифах разные фильтры стоят. На lite “более сильный DPI”
А может быть от адреса подключения зависит
У меня тариф “Земля”. Все нормально и по v4, и по v6

Разработчики СКАТ DPI (VAS Experts) выпустили статью о работе с QUIC в своём блоге.

На мобильном мегафоне возможно тоже такое есть: пришлось сменить симку в модеме на сим с другим тарифом, и прошлые настройки zapret перестали работать

Kathrin Elmenhorst, an author of “Web Censorship Measurements of HTTP/3 over QUIC”, has a repository with further tests of QUIC blocking, derived from OONI measurements. One thread of investigation is about Russia, and it has some interesting observations.

In particular, on Yota (AS 31213), there appears to be a two-layer filter. One layer blocks all QUIC version 1, except to specific servers; and a second layer blocks QUIC version 1 with with specific SNI values, no matter the server. This is interesting because the second layer means they are decrypting the initial packet protection on the packet containing the Client Hello.

The evidence for this comes from observing what happens when accessing different servers using different SNI values.

condition result
Access foreign server with correct SNI blocked
Access foreign server with vk.com SNI blocked
Access vk.com server with vk.com SNI works
Access vk.com server with www.facebook.com SNI blocked

Всё ещё наблюдается?
Сколько пакетов пропускает до блокировки (на картинке сквозная нумерация, число неочевидно)? Сохранились дампы?

Наблюдается. Там хаотичное поведение. 1-2 сессии могут пройти, 3 - застрять
сколько пакетов я не считал
2 провайдера с ТСПУ дают одинаковое поведение

Система может расшифровать QUIC_V1 и декодировать SNI.

Если ограничиться тестовым набором или соблюдать тестовые размеры, тогда всё работает: SNI декодируется и принимается решение.

Система не может или не хочет достать SNI из пакета, который генерирует Firefox. Возможно дело в размерах (например для структур далее по стеку). Недоступный SNI триггерит блокировку.

Приведите пример пакетов, которые фильтруются и не фильтруются. Выложите pcap’ы или содержимое пакетов.

blocked_firefox.bin (1.3 KB)
works_quictls.bin (1.2 KB)

Еще раз проверил на своем провайдере с ТСПУ
Ничего не поменялось. Нет никаких признаков в пользу предположения о декодировании SNI
Действительно, blocked_firefox.bin не проходит, а works_quictls.bin проходит
Оба они от example.com. Подозреваю, там стоит фильтр по некоторым полям или длинам без декодирования, который почему-то не срабатывает в одном случае
Пробовал curl с quiche на свой vps с разными SNI, в том числе vk.com. Ничего не проходит
На белосписочные IP от vk проходит все

./curl -4 --connect-to ::vk.com:443 --http3 https://example.com
curl: (55) SSL: no alternative certificate subject name matches target host name 'example.com'

При пробивании нулевым фейком или пакетом works_quictls.bin идентичное поведение. Хаотичный вис при загрузке сайтов

facebook_quictls.bin (1.2 KB)
Этот проходит?

Есть, как минимум, проверка целостности: если изменить хоть один байт в works, то пакет не доходит.
Планирую провести детальные исследования, но если кто-то готов перехватить эстафету, буду только рад.

Нет

Значит, все же расшифровывают. В QUIC initial нет никаких контрольных сумм, там используется GCM для аутентификации. Это AEAD шифр. Authenticated Encryption with Associated Data.
Шифрование и аутентификация завязаны в том числе и на нешифрованный хедер, чтобы любое изменение порождало неверный atag. И только полная дешифровка с проверкой atag позволяет отсечь коррупцию в любом байте пакета.
Однако, в остальных предложенных вариантах с этим тоже все в порядке.
Значит они или ориентируются на расшифрованный блок (почему не секут тогда SNI ?), или расшифровывают исключительно для проверки целки, а блочат по другим признакам типа длины
Или может быть в некоторых случаях они могут просечь SNI и блочат по нему, а в других случаях не могут (несовершенство парсера дешифрованного блока ?) и блочат, если IP не в white list
nfqws может распарсить все предложенные бинарики

Воспроизводится для гугла при запросе из curl+quictls, не воспроизводится для запросов из Firefox. (Внешнее различие в размерах пакетов, у Firefox они больше, а гугл-сервер в выборе размера пакетов ориентируется, в том числе, на размер QUIC initial клиента.)

До принятия решения о блокировке проходит в среднем 15 входящих пакетов. (Возможно применяется скользящее окно, поэтому не следует ориентироваться на число пакетов с начала сеанса). Выглядит как статистический анализ? Ложно триггерит правило блокировки psiphon или иного протокола (не связанного с QUIC)?

Не понятно столь широкое применение правила, возможно есть ограничение на адреса/порты/паттерны_inside.

This.

Возможно SNI успешно извлекается везде, а за блокировку по неизвестным паттернам из расшифрованного QUIC initial отвечает другое правило.

Можно сравнить пакеты от Firefox, quictls и quiche.
В коллекцию следует добавить legal_sni_quiche.bin

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