В этом посте я приведу небольшое исследование механизма блокировки сайтов Ростелекомом, а также покажу способы ее обхода без применения различных туннелей до сторонних хостов (прокси, vpn и пр.). Вероятно это применимо и к некоторым другим провайдерам.
Результат работы блокировки
HTTP-сайты РТ уже некоторое время блокирует по URL, а не по IP.
При блокировке в ответ приходит редирект вида 95.167.13.50/?st=0&dt=<IP>&rs=<URL>, где <IP> — IP, к которому подключался браузер, <URL> — URL, который он запрашивал. Если просмотреть передаваемый трафик, то становится видно, что перезаписывается лишь начало ответа сервера, остальное остается как есть.
HTTP/1.1 302 Found Connection: close Location: http://95.167.13.50/?st=0&dt=192.237.142.117&rs=grani.ru/ f-8 Transfer-Encoding: chunked Connection: keep-alive 6d7 <!DOCTYPE HTML> <html lang="ru" xmlns:fb="http://www.facebook.com/2008/fbml"> <head> <meta charset="utf-8"> <title> Грани.Ру: Главное </title> ...
HTTP/1.1 200 OK Server: nginx/1.2.1 Date: Sun, 01 Feb 2015 17:34:03 GMT Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive 6d7 <!DOCTYPE HTML> <html lang="ru" xmlns:fb="http://www.facebook.com/2008/fbml"> <head> <meta charset="utf-8"> <title> Грани.Ру: Главное </title> ...
Т.е. найдя способ восстановить заголовки, можно обходить блокировку. Очевидно, это не самый доступный способ.
Что и как блокируется
Очевидно, что блокировки сайтов у РТ на ручном контроле.
Не все сайты из реестра блокируются. Как минимум, есть несколько HTTPS-сайтов, которые никак не блокируются.
Обычно HTTPS-сайты блокируются по IP, иногда провайдер лезет и в HTTPS, подставляя свой сертификат, в таком случае происходит блокировка по URL.
Иногда HTTPS-сайт из реестра блокируется только по HTTP (соответственно по URL, а не по IP) и спокойно доступен по HTTPS.
Исследуем глубже
В ходе ряда экспериментов были выявлены следующие принципы работы блокировки:
- В первой строке запроса ищется название HTTP-метода, пробел, URL, пробел или? или /.
Реагирует на методы GET, POST, HEAD, DELETE, OPTIONS, TRACE. Метод PUT, видимо, забыли, его пропускает. Прочие названия методов тоже пропускает. Названия методов с измененным регистром символов тоже пропускает.Проверка происходит только в первой строке, если в начало запроса вставить пустую строку, то запрос проходит.
Если URL равен "/", то ищется только название метода.При добавлении лишнего пробела после названия метода запрос также проходит без проблем, если URL не равен "/".
По всей видимости считается, что URL закончился, когда встречаются символы пробел, "?" или "/". Если дописать к URL какой-то иной символ, то запрос проходит. В том числе, если дописать символ перевода строки, т.е. убрать " HTTP/1.1" из запроса. - Кодирование URL (urlencode) не помогает преодолеть цензуру, в том числе в разных регистрах. Даже если закодировать начальный слэш (%2F), то запрос блокируется, хотя веб-сервер такого уже не понимает.
- Далее ищется заголовок Host.
Причем ищется в том же пакете.
Причем ищется с обязательным соответствием виду «Host: <HOST>». Любой лишний символ или изменение регистра названия заголовка (host, HOST) позволяет запросу пройти.
Изменение регистра символов самого домена однако не помогает, блокировка срабатывает.
Способы обхода
Таким образом, приходим к следующим способам обхода:
- Добавление пустой строки в начало запроса. Не все веб-серверы понимают, в частности nginx не понимает.
- Добавление пробела перед URL. Это понимают популярные веб-серверы. Однако могут быть проблемы в редких случаях (например как здесь)
- Добавление какого-то символа после URL. Очевидно, это должен быть какой-то символ, который проигнорирует веб-сервер, но цензурный агрегат решит, что это часть URL. Я не смог найти такого символа.
- Убрать название протокола и версию (" HTTP/1.1"). В таком случае запрос воспринимается веб-сервером как HTTP/1.0, а в этой версии протокола не было заголовка Host, поэтому со многими сайтами это работать не будет.
- Отправка URL и Host разными пакетами.
Можно просто сначала вызвать send для первой строки запроса (HTTP-метод и URL), а затем обычным образом отправлять остальную часть запроса.
Можно добавить какой-то достаточно большой заголовок (порядка 1530 байт, чтобы наверняка заполнить весь пакет) между этими строками.
Проблем с веб-серверами в таких случаях не выявлено. - Модификация заголовка Host.
Можно менять регистр, добавлять пробелы перед доменом и после него.
Проблем с веб-серверами в таких случаях не выявлено.
Практическая реализация
Я выбрал реализацию на базе 3proxy. В его состав входит плагин, который позволяет модифицировать все передаваемые данные на основе регулярных выражений. При этом прокси довольно легкий и нетребовательный, возможна установка на обычный роутер.
В соответствии с вышеизложенным наиболее удобными вариантами на практике представляются добавление лишнего заголовка перед Host и модификация заголовка Host. Очевидно, модификация Host предпочтительнее, т.к. не увеличивает размер запроса. Я регулярно использую этот метод для того, чтобы самому решать какую информацию мне можно потреблять.
Но вообще оба варианта легко настраиваются:
pcre_rewrite cliheader dunno "Host:" "X-Something: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000rnHost:"
pcre_rewrite cliheader dunno "Host:" "HOST:"
# dns-сервера nserver 77.88.8.8 nserver 8.8.8.8 # кэшируем dns nscache 65536 # работе в фоне daemon # подключение плагина, стоит указать полный путь plugin PCREPlugin.ld.so pcre_plugin # одно из правил описанных выше pcre_rewrite ... # запуск прокси, опция -a позволяет избавиться от заголовков Forwarded-For и Via proxy -a -p8080
UPD:
ValdikSS внес весьма интересное замечание:
Стоило вам присмотреться к трафику, который приходит на интерфейс от Ростелекома. Вероятно, DPI подключен параллельно, а не последовательно, и туда приходит только клиентский трафик. Т.к. DPI стоит явно ближе, чем вебсайт, пакет с Location от DPI приходит быстрее, чем реальный первый пакет от сайта, а пакет от сайта уже отбрасывается ядром ОС как ретрансмиссия, поэтому, если вы используете Linux, достаточно одной строки в iptables, чтобы обойти блокировку:
iptables -A INPUT -p tcp --sport 80 -m string --algo bm --string "http://95.167.13.50/?st" -j DROP
От меня:
Действительно, есть ретрансмиссия. Я смотрел трафик, но очевидно недостаточно внимательно.
Сначала приходит пакет, в котором только HTTP 302 и Location, затем приходит пакет с нормальным ответом сайта.
Однако система не отбрасывает второй пакет, а своеобразно объединяет с первым.
Т.е. приходят пакеты1HTTP/1.1 302 Found Connection: close Location: http://95.167.13.50/?st=0&dt=192.237.142.117&rs=grani.ru/2HTTP/1.1 200 OK Server: nginx/1.2.1 Date: Sun, 01 Feb 2015 17:34:03 GMT Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive 6d7 <!DOCTYPE HTML> ...А приложение видит это
такHTTP/1.1 302 Found Connection: close Location: http://95.167.13.50/?st=0&dt=192.237.142.117&rs=grani.ru/ f-8 Transfer-Encoding: chunked Connection: keep-alive 6d7 <!DOCTYPE HTML> ...Это наблюдается и в Windows и в Linux.
Но приведенное правило iptables действительно решает вопрос.
Так что +1 способ обхода.
Этот способ можно использовать и на шлюзе/роутере. Правило при этом конечно же нужно добавлять в цепочку FORWARD.
Автор: peter23