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

    В этом посте я приведу небольшое исследование механизма блокировки сайтов Ростелекомом, а также покажу способы ее обхода без применения различных туннелей до сторонних хостов (прокси, 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.
    Share post

    Similar posts

    Comments 42

      +2
      Многое уже было описано до вас:
      Определитель типа блокировки сайтов у провайдера habrahabr.ru/post/228305/
      Статистика применяемых средств блокирования сайтов habrahabr.ru/post/229377/
      tl;dr -> github.com/ValdikSS/blockcheck
        +3
        Тут я несколько иное описал — подробное изучение конкретного метода блокировки у конкретного провайдера (одного из крупнейших в РФ). И выводы о способах его обхода.
          +9
          Стоило вам присмотреться к трафику, который приходит на интерфейс от Ростелекома. Вероятно, 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
            0
            Действительно, есть ретрансмиссия. Я смотрел трафик, но очевидно недостаточно внимательно.
            Сначала приходит пакет, в котором только 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.

            Но приведенное вами правило действительно решает вопрос.
            Добавлю в пост :)
        0
        Интересно, а можно ли через плагины заставить Firefox или Chrome игнорировать вредоносные редиректы на адрес Ростелекома?
          +1
          Там не просто нужно игнорировать редирект, а именно восстановить заголовок. В частности Content-Type часто затирается.

          Если плагины имеют права на модификацию заголовков, то это возможно. В таком случае и прокси не понадобится.
          +1
          уважаемый автор, подскажите пожалуйста, каким образом оператор заставит пользователей доверять своему сертификату для того, чтобы залезть в https
            +5
            Никак. Когда они залазят в https, пользователи видят в браузерах сообщение о подмене сертификата.
            Они несколько раз включали такой механизм для https-сайтов из реестра, потом отключали.
              0
              В Squid 3.2.x, например, есть SSLBump. На каждый домен (например, vk.com) динамически генерится сертификат, подписанный своим CA. Единственное условие — чтобы этот CA был установлен как доверенный в браузере пользователя (мой ответ неточный, но смысл в целом такой). Я пользовался этой штукой, чтобы фильтровать https-трафик и собирать статистику юзеров на одном предприятии.

              Статья на тему: habrahabr.ru/post/168515/
                0
                Если какой-то CA начнет так делать, то сертификат этого CA будет отозван из списка доверенных.
                Ростелеком, когда начинает лезть в https, подписывает все своим собственным сертификатом, выданным для *.rt.ru. Либо у РТ нет своего CA, либо они им не рискуют.
                  0
                  А как жестко забанить у себя все сертификаты *.rt.ru, даже те, которые еще не появились? Так, на всякий пожарный.
                    0
                    Для винды можно так попробовать — создать через makecert самоподписанный сертификат с CN=*.rt.ru и положить его в Untrusted certificates (certmgr.msc), хотя у меня есть сомнения в правильности этого способа.
                    • UFO just landed and posted this here
                        0
                        Спасибо за совет, сохранил их сертификат, добавил в «Untrusted certificates» — при открытии страницы https://rt.ru IE и Chrome ругаются на отозванный сертификат, а FF почему-то нет.
                          0
                          У FF своя база сертификатов, нужно добавлять в самом FF… Вот только как там добавить исключение именно для блокировки — не знаю…
                0
                Элементарно. Люди периодически видят предупреждения о самоподписанных сертификатах и дают разрешение на использование, особо не вчитываясь.
            • UFO just landed and posted this here
                0
                Да, я писал
                Отправка URL и Host разными пакетами.
                Можно просто сначала вызвать send для первой строки запроса (HTTP-метод и URL), а затем обычным образом отправлять остальную часть запроса.
                  0
                  В iptables — одна строчка в таблице mangle
                  -A FORWARD -o eth1 -p tcp --tcp-flags SYN,RST SYN -m tcp --dport 80 -j TCPMSS --set-mss 10
                    0
                    По идее это не очень хорошо, ведь это приведет к тому, что весь обмен трафиком с сервером будет идти пакетами по 10 байт. Или я что-то упускаю?
                      0
                      Совершенно верно. «не очень хорошо» означает всего лишь увеличение служебного трафика. В случае IPv4 это 40 байт заголовков на каждый пакет. При современных каналах IMHO проблема невелика.
                        0
                        40 байт на каждые 10? Т.е., в пять раз?
                      +3
                      Это очень плохо, это будет нереально тормозить интернет. Как крайний выход — устанавливать MSS, равный 10, только на заблокированные IP. Но вообще, по идее, в интернете нельзя использовать MSS меньше, чем 536 для IPv4 и 516 для IPv6.

                      Вообще, вот скрипт для OpenWRT и Ростелекома. Блокирует пакет от DPI на 80 порту, блокирует подмену сертификата для HTTPS.
                      github.com/proukornew/rt_iptables/blob/master/ipset.sh
                        0
                        О, спасибо, стащу к себе часть кода добавлялки дополнительных IP в фильтр (если они не резолвятся). Как будет возможность, потестирую на ростелекоме для Zyxel Keenetic с прошивкой первого поколения (там почти штатно сродни OpenWRT, но отличия есть)
                  –1
                  С дом.ру/эртелекомом не сработает, ответ стабильный:

                  HTTP/1.1 302 Moved Temporarily
                  Cache-Control: no-cache,no-store,max-age=1
                  Pragma: no-cache
                  Expires: Thu, 01 Jan 1970 00:00:00 GMT
                  Connection: close
                  Content-Length: 13
                  Location: http://lawfilter.ertelecom.ru
                  
                  <html></html>
                  

                  Реагирует только на GET, на регистр и пробелы пофиг.
                    0
                    Интересно было бы поэкспериментировать. Можете дать VPN на роутер? :)

                    Ну или попробуйте разные описанные варианты, в частности передачу заголовка Host в отдельном пакете.
                      0
                      У домру же вроде DNS-запросы подменяются (даже если использовать сторонний сервер или свой резолвер), если DNS-сервер подцеплять поверх шифрованного канала типа DNSCrypt или банального OpenVPN, то по идее будет работать
                        0
                        Уже не только DNS. :(
                      +1
                      > а в этой версии протокола не было заголовка Host, поэтому со многими сайтами это работать не будет.
                      Не не было, а был не обязательным.

                      Иначе бы, например, proxy_pass в nginx никогда не работал бы — он очень долгое время работал по HTTP/1.0 =)
                        0
                        > # кэшируем dns
                        > nscache 65536

                        А за такое нужно бить тапком по пальцам.
                          0
                          Без кэширования DNS-запросов прокси будет дергать DNS на каждый запрос. Очевидно, это будет приводить к задержкам.
                          А в чем вообще проблема с кэшированием в данном случае? Это же собственный прокси для собственного серфинга. Без использования прокси тоже самое кэширование DNS делает сам браузер.
                            +1
                            В DNS и так есть кеширование. И есть TTL, на который полагаются админы и вообще команды сервисов.
                            А на практике, из-за таких рекомендаций, как выше, ещё месяц-два после смены dns-записи трафик ломится на старый IP.
                              +1
                              В описанном мной случае смысл кэшировать DNS определенно есть, равно как есть смысл кэшировать DNS в браузере.
                              Без кэширования DNS происходило бы следующее:
                              — пришел запрос на www.example.com/, дернули DNS, только после получения ответа DNS можем обрабатывать запрос
                              — пришел запрос на www.example.com/scripts.js, дернули DNS, только после получения ответа DNS можем обрабатывать запрос
                              — пришел запрос на www.example.com/style.css, дернули DNS, только после получения ответа DNS можем обрабатывать запрос
                              и т.д.
                              Очевидно, что даже при наличии локального DNS вносятся заметные задержки.
                              HTTP Keep-Alive конечно сгладил бы негативный эффект, но не отменил бы его.

                              Вообще в данном случае прокси, кэшируя DNS, делает тоже самое, что и браузер.
                              При работе через прокси браузер не посылает DNS запросы, это делает прокси.
                              Поэтому и кэшировать DNS тоже должен прокси.

                              Что касается TTL, то я уверен, что 3proxy (как и браузер) учитывает его в алгоритме кэширования.
                                +2
                                > Вообще в данном случае прокси, кэшируя DNS, делает тоже самое, что и браузер.
                                Вот только браузеры TTL учитывают.

                                > Что касается TTL, то я уверен, что 3proxy (как и браузер) учитывает его в алгоритме кэширования.
                                Нет. В данном примере написано «любую dns-запись кешировать на 65536 секунд». Для примера, у меня TTL — 360 секунд на всех хостах.
                                  +1
                                  На самом деле в данном примере написано «кэшировать DNS, размер кэша 65536 записей».
                                  65536 это лишь размер кэша, но не время кэширования.
                                    +1
                                    Хым. И правда.
                                    Пардон тогда.
                          0
                          Для блокировки HTTPS трафика, многие DPI также смотрят на поле SNI, что позволяет блокировать не по IP, а по доменному имени. Фильтровать конкретный URI в пределах домена «увы» не получиться.

                          Only users with full accounts can post comments. Log in, please.