Pull to refresh

Исследование механизма блокировки сайтов «Ростелекомом» и способы ее обхода

Reading time 6 min
Views 103K
В этом посте я приведу небольшое исследование механизма блокировки сайтов Ростелекомом, а также покажу способы ее обхода без применения различных туннелей до сторонних хостов (прокси, 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: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\r\nHost:"
Модификация заголовка
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, затем приходит пакет с нормальным ответом сайта.
Однако система не отбрасывает второй пакет, а своеобразно объединяет с первым.
Т.е. приходят пакеты
1
HTTP/1.1 302 Found
Connection: close
Location: http://95.167.13.50/?st=0&dt=192.237.142.117&rs=grani.ru/

2
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>
...
А приложение видит это
так
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.
Tags:
Hubs:
+50
Comments 45
Comments Comments 45

Articles