В код клиентов Telegram добавили возможность маскировки под HTTPS (TLS + HTTP/2.0).



Для использования этой возможности добавили новый префикс секрета — «ee». Кроме того, добавили возможность кодировать секрет в адресе прокси сервера как base64, в дополнение к hex.

Перед тем, как углубиться в детали, попробуем разобраться как развивалась поддержка прокси-серверов в Telegram.

Предыстория


  1. Сначала Телеграм поддерживал SOCKS-прокси. Это помогало обойти блокировку серверов по IP, но протокол был заметен в трафике, а пароль передавался в открытом виде
  2. Примерно год назад выпустили официальный прокси, работающий по новому протоколу MTProto. В отличие от SOCKS, пароль в MTProto не передавался в открытом виде. В протоколе избавились от каких-либо служебных заголовков, по которым можно было бы понять что это действительно он. Ещё добавили возможность показа рекламы пользователям прокси-сервера
  3. Оказалось что прокси-серверы, работающие по протоколу MTProto, можно обнаружить по длине пакетов. При установке соединения клиент и прокси сервер обмениваются пакетами определённой длины, а при работе — пакетами одной и той же длины по модулю 4. Эта особенность начала использоваться крупными провайдерами для блокировки мессенжера. Разработчики Telegram отреагировали на это модификацией протокола, добавив в каждый пакет некоторое количество случайных байт. Так как изменение ломало совместимость, пришлось дополнить формат секрета специальным префиксом «dd», означающим использование модифицированного протокола:
    tg://proxy?server=178.62.232.110&port=3256&secret=dd00000000000000000000000000000000
  4. При изучении особенностей блокировок прокси серверов в Китае и Иране выяснилось, что надзорные органы используют для детекта replay-атаки. В альтернативных реализациях прокси-серверов на Python, Erlang и Go появилась частичная защита от такого вида атак. Для этого прокси серверы запоминают данные, передающиеся на начальном этапе установки соединения и не дают повторно соединиться с такими же данными. У подхода есть проблема с крупными прокси-серверами, т.к. для запоминания требуется большое количество оперативной памяти
  5. В Китае и Иране применяют следующую тактику: если протокол неизвестен, то на всякий случай скорость его работы сильно режут. На практике это означает возможность использования Telegram только для передачи текстовых сообщений, без картинок и видео. Причём в Китае так умели делать давно, а в Иране научились относительно недавно. В России пока не научились, но закон уже приняли. Попытка разработчиков мессенжера замаскировать трафик под какой-нибудь популярный протокол на этом фоне выглядит закономерно.

Что изменилось?


В протоколе между клиентом Telegram и прокси-сервером добавили ещё один слой инкапсуляции поверх TCP. Вместо посылки данных по TCP, данные оборачиваются в записи TLS следующего вида:



В начале работы добавился этап эмуляции TLS-handshake. Пакет от клиента к прокси-серверу имеет такую структуру:



Почти все поля не имеют для клиентов Telegram смысла и нужны лишь для того чтобы прикидываться TLS. Самая важную функцию несёт поле Random, куда помещается результат HMAC от общего секрета и данных в пакете, что позволяет доказать клиенту что он знает секрет. Также, клиент ксорит последние 4 байта поля Random со своим временем в формате unixtime, что позволяет прокси-серверу определять когда был сгенерирован пакет. Это полезно для защиты от replay-атак. Если пакет сгенерирован давно или в будущем, то прокси-сервер может его сразу отбросить.

При подключении клиента, прокси сервер проверяет переданный HMAC. Если он совпадает с вычисленным, прокси отвечает пакетом со следующей структурой:



Поле Random в нём также не является случайным, а является результатом HMAC от общего секрета и данных в пакете, причём при вычислении HMAC, случайное значение, отправленное клиентом приписывается перед данными самого пакета. При передаче самих данных, первая посылка игнорируется клиентом, что позволяет послать ему данные случайной длины, для дополнительного усложнения обнаружения.

Где попробовать?


Для демонстрации был доработан и поднят прокси-сервер на языке Python, к которому можно подключаться десктопным клиентом Telegram последних версий и смотреть передающийся трафик с помощью Wireshark:

tg://proxy?server=178.62.232.110&port=3256&secret=7gAAAAAAAAAAAAAAAAAAAABnb29nbGUuY29t

Также, поддержка маскировки под TLS была добавлена в прокси-сервер на языке Erlang. Скорее всего, в ближайшее время, данную функциональность добавят и в другие реализации прокси-серверов.

Комментарии (139)


  1. zzzmmtt
    12.08.2019 09:13
    +3

    Я просто оставлю это здесь.


    1. shifttstas
      12.08.2019 09:19
      +5

      Этот пост так же хороший и приводит новые технические подробности.


      PS: если я правильно понял, автор поста — автор прокси на Python который один из первых и добавил поддержку.


      1. alexbers Автор
        12.08.2019 09:28
        +6

        Да, всё так. Написал эту статью параллельно с добавлением поддержки, но, видя как авторы телеграм маскировали её в коммитах бета-версий, решил подождать релиза, чтобы не дать создателям средств DPI времени подготовиться.


        1. shifttstas
          12.08.2019 09:35
          +1

          Не планируете реализовать «слив» трафика с другим SNI на другой порт?


          Т.е когда к вам идёт запрос не на домен который указан в прокси а на другой — показывать обычный сайт.


          1. alexbers Автор
            12.08.2019 09:38
            +2

            Как раз экспериментирую с этим.


            1. Whuthering
              12.08.2019 09:46
              +1

              Я бы сделал чюдаже по-другому — если после расшифровки TLS прокси видит, что это валидный телеграм-коннект, то обрабатывать его как надо, а если нет — пересылать туда-сюда на порт локального веб-сервера, где котики и зеркало Википедии. Так сказать, защита от https probing.


              1. shifttstas
                12.08.2019 09:48

                Это фактически нужно проверять ключ, если он не подошёл — отправляем в порт веб сервера. И да — это самый лучший вариант. Допускаю, что для этого придётся использовать реальный домен + letsencrypt для автоматизации.


          1. Whuthering
            12.08.2019 09:43

            SNI-имена в пакете не криптуются (вроде в TLS1.3 завезти обещали только), поэтому для DPI это не будет проблемой. Так что польща будет разве что только из-за сильного желания пошарить 443 порт на белом айпи… :)


            1. shifttstas
              12.08.2019 09:46

              Ну так со временем будет eSNI и это будет иметь смысл.


            1. AlxDr
              12.08.2019 11:55

              Это и сейчас можно сделать разными способами, используя сторонний мультиплексор типа SSLH или даже средствами nginx.

              Но было бы гораздо практичнее, особенно для чайников, если бы это было реализовано на уровне прокси. Да и обнаружение было бы гораздо сложнее, сейчас оно всё-таки возможно, если система контроля активная и сама пощупает порт.


              1. shifttstas
                12.08.2019 11:59

                Нет нельзя, сейчас домен А может использоваться только: либо телеграм либо веб-сервер.


                Доработке прокси позволит использовать один и тот же домен А и для прокси и для веб-сервер. Отправляя запросы на последний в случае ошибки в секрете.


                1. AlxDr
                  12.08.2019 12:27
                  +1

                  Отправляя запросы на последний в случае ошибки в секрете.


                  Да, это наиболее верный подход, как по мне.


                1. inkvizitor68sl
                  12.08.2019 16:17

                  Можно вообще использовать какой-то отдельный урл для проксирования. Вряд ли сканилка в https найдет что-то вроде domain.com/0aihs9iqwhrhiasdihfhoiaoihsf/
                  А на самом домене вешать уже нормальный сайт.


                  1. seriyPS
                    12.08.2019 22:22

                    URL в TLS mtproto proxy протоколе не используется


                    1. inkvizitor68sl
                      12.08.2019 22:27

                      Ну так самое время начать. Это же очевидное решение — сто лет назад люди вешали php-анонимайзеры на отдельный урл своего сайта. А теперь https и вовсе не позволит найти этот урл, если его хоть немного спрятать.


                      1. seriyPS
                        13.08.2019 00:42

                        Ну для этого придётся полноценный TLS реализовывать. То что в mtproto proxy — хоть и маскируется под TLS 1.3, но внутри совсем по другому устроено.


                        1. inkvizitor68sl
                          13.08.2019 02:43

                          Ну так это как раз и есть то место, где нужен полноценный https. Как DPI будут бороться с мессенджером, для поднятия прокси которому достаточно на любой свой сайт закинуть по какому-нибудь хитрому урлу php-файл? А если в клиент можно вбивать такие урлы сотнями?
                          Зачем нужен mtproto proxy, который прямо таки кричит в любой DPI помощнее «вот он я, берите меня, unknown proto!» — тоже неясно.


                          1. sumanai
                            14.08.2019 11:22

                            Зачем нужен mtproto proxy, который прямо таки кричит в любой DPI помощнее «вот он я, берите меня, unknown proto!» — тоже неясно.

                            Очевидно, чтобы вставлять рекламу. Сейчас это модно. Купив новый телефон, я офигел от количества рекламы изо всех щелей.
                            Ну и реклама типа должна стимулировать поднимать прокси.


                          1. TheChief5055
                            14.08.2019 11:47

                            mtproto proxy, который прямо таки кричит в любой DPI помощнее «вот он я, берите меня, unknown proto!»


                            Почему? DPI никакого URL внутри TLS не увидит, как и не увидит того, что они не используются. Максимум — SNI (так никто не мешает указывать реальный dyndns-хост). DPI видит, что «на хост ходят по TLS» и всё. Расшифровать содержимое оно не может, не зная секрета. Попробует зайти на хост само — получит страничку с котиками (если в проксе настроен проброс невалидных коннектов на реальный веб-сервер).

                            Где я ошибаюсь, полагая, что у DPI нет критериев для вынесения предположения о содержимом TLS1.3 сессии?


                            1. inkvizitor68sl
                              14.08.2019 21:01

                              > DPI никакого URL внутри TLS не увидит,
                              Зато увидит, что работающего TLS на втором конце нет.


                              1. TheChief5055
                                14.08.2019 21:12

                                Зато увидит, что работающего TLS на втором конце нет.


                                Я и прошу объяснить — КАК? По каким признакам?


                                1. inkvizitor68sl
                                  15.08.2019 18:08

                                  Ткнётся активным сканером и посмотрит, что там.


              1. Whuthering
                12.08.2019 12:11

                Это и сейчас можно сделать разными способами, используя сторонний мультиплексор типа SSLH
                Нельзя. SSLH не расшифровывает трафик, он его проверяет по очень простым сигнатурам и раскидывает в зависимости от этого по портам. Если sslh без расшифровки различит, то DPI уж и подавно.


                1. AlxDr
                  12.08.2019 12:31

                  Речь о том, что без FakeTLS пошарить порт на белом айпи таки можно :)

                  Впрочем, я думаю, что и с ним можно, если речь именно о белом айпи, а не домене — средствами nginx. Но смысла лично я мало вижу, подожду решения с проверкой ключа.


                1. TheChief5055
                  14.08.2019 13:56

                  SSLH не может различить «просто TLS» и «OpenVPN в „tls-crypt“ режиме». По крайней мере, у меня это не заработало ни на 1.18-1 из штатных реп, ни на самособранном 1.20.


          1. AlxDr
            12.08.2019 11:49
            -1

            SNI же виден при установке соединения TLS.
            И поэтому использование какого-то особого SNI — сразу палево, разве нет?

            Нужен мультиплексор по ключу, как описано ниже. Если ключ подходит, то соединять с прокси, если нет, то передавать на веб-сервер, который может обрабатывать трафик как угодно, у него могут быть несколько виртуальных доменов даже.

            Телеграм-клиенты при этом должны как раз корректный SNI использовать, чтобы внешне не отличаться от стандартных веб-клиентов.


          1. alexbers Автор
            13.08.2019 00:38
            +2

            Закончил экспериментировать и закоммитил эту функциональность. Сейчас можно указывать хост и порт на который будет незаметно проксироваться трафик "плохих" клиентов.


            Попробую продемонстрировать как это работает:


            • есть сайт который выглядит для браузера "обычно": https://bitblockinfo.com/
            • в то же время он является замаскированным прокси сервером tg://proxy?server=bitblockinfo.com&port=443&secret=7gARIjNEVWZ3iJmqu8zd7v9iaXRibG9ja2luZm8uY29t.

            Это сделано для того, чтобы нельзя было задетектить прокси-сервер по особенностям работы с TLS.


            1. Revertis
              13.08.2019 00:56
              +1

              Вот это офигенно!


            1. TheChief5055
              13.08.2019 12:15

              Супер! И это у меня заработало в цепочке: openvpn (shared port) > mtprotoproxy > apache2. Апач, конечно же, показывает кошечек и порно. Всё на стандартном 443. «Вы знаете? Я счастлив.» © Друпи.

              PS: мобильный Telegram (андроид) сходит с ума от сгенерированной и втянутой ссылки на прокси с более-менее длинным TLS_DOMAIN. И просто зависает.


              1. alexbers Автор
                13.08.2019 12:16

                Советую посмотреть это подробнее, вдруг там буфер переполняется и можно хакнуть.


                1. TheChief5055
                  13.08.2019 12:31

                  Возможно, но делать ревизию кода мне не по силам.

                  Я обратил внимание, что сгенерированный секрет с доменом google.com не содержит в конце символа суффикса «=». И с ним моб.клиент работает. А вот с моим доменом секрет имеет в конце суффикс «=» — и клиент ломается. Криво декодируется base64 в клиенте?


                  1. alexbers Автор
                    13.08.2019 12:42

                    Можно проверить, декодировав в обратную сторону. В клиенте для ios есть бага с декодированием некоторых символов base64.


                    1. Allineer
                      13.08.2019 14:13

                      В Telegram Desktop v.1.8.1 же вообще не удается вставить секрет с символами "=" или "%" — они просто не пропечатываются.
                      С моим доменом секрет получается в таком виде:
                      7u***************wjjNQRnYXRlLmtueXNob3YuaW5mbw%3D%3D

                      Кстати, а можно попросить объяснить, на что влияет значение TLS_DOMAIN в текущей реализации? Особенно учитывая, что можно произвольно изменить несколько последних символов секрета уже прописывая его в клиенте и тебе ничего за это не будет?


                      1. alexbers Автор
                        13.08.2019 14:19

                        Сейчас ни на что, кроме генерации ссылки в начале работы. Планирую его использовать на серверах с большим количеством секретов, чтобы по имени можно сразу было бы пробовать расшифровать с нужным секретом.


                        В ближайший час планирую поменять вывод адреса прокси по умолчанию, чтобы не использовалось base64 пока авторы Telegram не починят клиента для ios.


                      1. alexbers Автор
                        13.08.2019 14:28

                        Поменял код, чтобы в ссылках на прокси не использовался base64. Верну обратно когда пофиксят.


                        1. TheChief5055
                          13.08.2019 14:40

                          Данная правка помогла также андроид-клиенту (ошибка, видимо, у них общая). Огромное спасибо!


            1. Whuthering
              13.08.2019 13:56

              У меня почему-то не работает.
              Сервер стартует, пишет в консоль строку подключения.
              Пытаюсь соединиться с клиента Android v5.10.0 (1684) — висит в «Connecting...» и всё. Процесс живой. Со старым протоколом (dd-) соединяется нормально. Если браузером попробовать зайти на IP и порт mtproxy, то показывается страница, куда стоит редирект с MASK_HOST и MASK_PORT в конфиге.

              Cryptography пакет стоит

              # pip3 install cryptography
              Requirement already satisfied: cryptography in /usr/local/lib/python3.7/dist-packages (2.7)
              Requirement already satisfied: six>=1.4.1 in /usr/lib/python3/dist-packages (from cryptography) (1.12.0)
              Requirement already satisfied: asn1crypto>=0.21.0 in /usr/lib/python3/dist-packages (from cryptography) (0.24.0)
              Requirement already satisfied: cffi!=1.11.3,>=1.8 in /usr/local/lib/python3.7/dist-packages (from cryptography) (1.12.3)
              Requirement already satisfied: pycparser in /usr/local/lib/python3.7/dist-packages (from cffi!=1.11.3,>=1.8->cryptography) (2.19)


              Upd. При этом десктопный клиент 1.8.1 через этот же прокси в tls-режиме работает без проблем. Мистика какая-то.


              1. Whuthering
                13.08.2019 14:53
                +1

                Всё заработало с последним коммитом, отключающим base64-кодирование в выводимой строке подключения.


                1. TheChief5055
                  13.08.2019 14:59
                  +1

                  Подтверждаю, мобильный клиент заработал с hex-секретом.


        1. Acuna
          13.08.2019 02:05

          Почему Python был выбран для разработки? Он же медленнее той же Ноды в пять раз (Нода даже С++ уже уделывает в некоторых областях). Понятно что это потому-что его и учили, ибо он довольно новый, но зачем учить один из самых медленных по всем тестам языков?


          1. alexbers Автор
            13.08.2019 02:16

            На практике проблемы производительности не возникает. Даже самой дешёвой виртуальной машины хватает чтобы обслужить несколько тысяч пользователей. Но столько пользователей набрать тяжело т.к. основные каналы распространения информации о прокси серверах мониторятся. При большом числе пользователей прокси сервер может быть заблокирован по паттернам трафика


            1. Acuna
              13.08.2019 02:24

              Понятно, но проблемы производительности и на PHP не возникает, хотя говорят что тормозный, а он очень даже, до JS конечно не дотягивает, но все же. Вот я и спрашиваю, почему именно Python? По мне это как с парашютом прыгать — можно ноги сломать, причем неиллюзорно так, но я же не прыгаю там, где можно ноги сломать, я на травку прыгаю.


              1. alexbers Автор
                13.08.2019 02:50

                У меня больше всего опыта работы с этим языком. Но есть и другие реализации разной степени продвинутости: на go, erlang, си, c#, nodejs, java, pony и т.д.


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


                1. Acuna
                  13.08.2019 03:01

                  Ого! Не знал что у него столько реализаций!) Ясно, спасибо, покопаюсь…


          1. python273
            13.08.2019 02:35

            он довольно новый

            лол


            https://en.wikipedia.org/wiki/Python_(programming_language)


            First appeared 1990; 29 years ago

            https://en.wikipedia.org/wiki/Node.js


            Initial release May 27, 2009; 10 years ago

            https://en.wikipedia.org/wiki/JavaScript


            First appeared December 4, 1995; 23 years ago

            Edit: добавил JS


            1. Acuna
              13.08.2019 02:41

              А, полагаю это и есть ответ на мой вопрос, давно его знают просто, вот и пишут) Просто как-то психологически не могу перестроиться на то, что это старый язык. Старыми считаются С, Java, да хоть PHP тот же, что уж там…


          1. batyrmastyr
            13.08.2019 17:51

            1) Нода не в пять раз быстрее Питона.
            2) «В некоторых областях» бывает всякое: и Free Pascal быстрее Си и C++, и Нода в пять раз медленее Питона, и PHP отстаёт от Go и C++ на смешные 7-15%.
            3) Нода память жрёт как не в себя.
            4) Js один из худших языков среди массовых.



      1. schors
        12.08.2019 09:28

        Правильно, да :)


  1. ne555
    12.08.2019 09:49
    -1

    Дубль.Может кому-то пригодится: здесь в статье на Хабре можно почитать статью про официальный халявный MTProxy, но главное в комментах кучу живых вариантов реализации MTProxy от python… разных docker образов и пд., которые уже успешно и давно используются в Telegram.


  1. vp7
    12.08.2019 09:53

    На самом деле не хватает последнего шага:


    1. Возможность серверу определять домен по SNI и если домен не попадает в заданный список, то пропускать коннект дальше (на обычный web сервер с заглушкой).
    2. Возможность работы через WebSocket'ы, работая в качестве backend'а для обычного web server'а типа nginx, при этом добавив минимальную http basic аутентификацию.

    Тогда прокси можно будет вешать на реальных живых сайтах, с реальным трафиком. Просто будет поддомен stat.mysite.ru или даже просто конкретный URL на сайте, закрытый http basic auth'ом.
    Даже если "постучался" — тебя послали нафиг, а владелец сайта (ну если вдруг ему начнут задавать вопросы) честно скажет "это закрытый раздел сайта".


    Ну а телега сможет сделать upgrade до вебсокетов и дальше кидать свой трафик, к примеру завернув туда тот самый mtproxy.
    Тот же nginx в качестве фронтенда великолепно подойдёт.
    И нет необходимости прикрываться именами типа google.com, наоборот веселее будет кому-то получить прокси на реальных серверах с реальным доменным именем типа bstat.gosuslugi.ru, закрывшись самым настоящим сертификатом *.gosuslugi.ru ;)))


    1. istepan
      12.08.2019 10:04

      Лучше даже просто кидать 504 Gateway Timeout по прошествии нескольких минут. Без соединения по факту.
      Первый плюс — у себя сэкономим трафик и ресурсы.
      Второе, — займем ресурсы проверяющего бота.


    1. alexbers Автор
      12.08.2019 10:16
      +1

      1. Тут даже можно пойти дальше — сделать один домен и если HMAC из поля random handshake-пакета не валидный, то редиректить. Сегодня вечером будет версия, которая так делает.
      2. Насколько я понял, тут нужна поддержка со стороны фронта или клиента Telegram. Для себя можно поднимать веб-версии Telegram, но маловероятно, что сторонний человек согласиться ввести свои credentials на сайт, не связанный с тг.


      1. vp7
        12.08.2019 10:32

        Обязательно нужна поддержка со стороны клиентов, причём тех, которые лежат в google/apple market'ах.
        Как минимум — возможность работы через WebSocket.


        Но если это реализовать, то блокировать телегу можно будет только тем самым "законом о суверенном интернете" с полной изоляцией российского сегмента сети. И то не факт ;)


  1. navion
    12.08.2019 10:14
    -2

    Это всё замечательно, но не спасёт от цензуры техногигантов: Telegram на iOS фильтрует некоторые каналы по указке калифорнийский леваков, хотя до этого блокировал лишь террористов, ЦП и за многократные нарушения DMCA.


    1. shifttstas
      12.08.2019 10:16
      -1

      Вам никто не мешает использовать не родной клиент или официальную веб-версию.


      1. AEP
        12.08.2019 15:34

        Официальная веб-версия, когда я ее последний раз проверял, поддерживала только текстовые чаты и вставку картинок, но не поддерживала звонки.


        1. shifttstas
          12.08.2019 15:40

          Так а что ещё нужно для просмотра заблокированного контента на платформе? Для звонков и всего — основной клиент, для заблокированного — веб версия.


    1. ne_kotin
      12.08.2019 12:59
      +1

      На android не фильтрует же? Пересаживайтесь на android, какие проблемы?


      1. AibekAS
        13.08.2019 08:52

        С фильтрацией все интересно)
        Место действия Казахстан.
        У меня, фильтруются всякие 18+ стикеры, каналы и они мне не доступны. Но моим друзьям, у которых я тестил акки, им все было доступно.


      1. Foorya
        13.08.2019 12:14

        Узбекистан, любая версия Телеграм (андроид, iOS и даже веб) блокирует 18+ каналы (якобы забанены за порнографию)


  1. Revertis
    12.08.2019 11:56

    Так а поставить MtProtoProxy за nginx'ом с валидными сайтами можно?
    Я пробовал когда-то с помощью SSLH, но тогда сайты видели все коннекты клиентов как от 127.0.0.1.


    1. AlxDr
      12.08.2019 12:37
      +1

      У SSLH есть «прозрачный режим», но его настройка несколько геморройна.

      Если пока отказаться от этого FakeTLS, то средствами nginx можно проверять версию TLS (например) и неизвестные версии отправлять на МТ-прокси. В этом случае снаружи будет виден порт 443, на него можно будет соединиться стандартным клиентом и увидеть веб-сайт, но соединения от телеграм-клиентов туда же будут видны как неведомая хрень. В теории это не должно быть основанием для блока, так как такой подход используется для OpenVPN и других задач, на практике же непонятно.


      1. Revertis
        12.08.2019 14:06

        Хм, а это интересно. Но что-то не получается. Вы ведь имели ввиду ssl_preread_protocol?


        1. AlxDr
          12.08.2019 15:08

          Да.
          Вероятно, nginx собран без модуля ngx_stream_ssl_preread_module?
          В пакетах по-умолчанию его почему-то нет, надо самому собирать.


          1. Revertis
            12.08.2019 15:23

            Да нет, вроде всё есть в пакетах:

            nginx version: nginx/1.16.0
            built by gcc 6.3.0 20170516 (Debian 6.3.0-18+deb9u1)
            built with OpenSSL 1.1.0j 20 Nov 2018 (running with OpenSSL 1.1.1c 28 May 2019)
            TLS SNI support enabled
            configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fdebug-prefix-map=/data/builder/debuild/nginx-1.16.0/debian/debuild-base/nginx-1.16.0=. -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'
            Может что-то не так делаю…


            1. AlxDr
              12.08.2019 15:34

              Может :)
              У меня работает примерно так:

              map $ssl_preread_alpn_protocols $upstream {
              default ssh;
              ~\bhttp/0.9\b web;
              ~\bhttp/1.0\b web;
              ~\bhttp/1.1\b web;
              ...


              Но там у меня по alpn и несколько другие задачи. На web висит веб-сервер с сайтами, на ssh всякая байдистика и мтпрото там тоже есть — полёт нормальный :)


              1. Revertis
                12.08.2019 15:38

                А вебсервер на порту, отличном от 443?


                1. AlxDr
                  12.08.2019 15:50

                  Конечно, как иначе :)
                  Схема примерно такая:
                  stream {
                  upstream ssh {
                  server localhost:550;
                  }

                  upstream web {
                  server localhost:410;
                  }


                  map $ssl_preread_alpn_protocols $upstream {

                  default ssh;
                  ~\bhttp/0.9\b web;
                  ~\bhttp/1.0\b web;
                  ~\bhttp/1.1\b web;
                  }

                  server {
                  listen 443;

                  proxy_pass $upstream;
                  ssl_preread on;
                  }
                  }


                  Соответственно, ниже в конфиге веб-сервер слушает порт 410, а 550 — там уже висит прокси и ещё кое-что.

                  Можно попробовать намудрить примерно то же самое, но с проверкой по версии TLS или ещё чему-нибудь.


    1. alexbers Автор
      13.08.2019 00:42

      Теперь можно.


  1. blind_oracle
    12.08.2019 13:45

    В хендшейке нет отправки сертификата сервером. И дальнейших шагов. DPI легко может блочить такую сессию как невалидную ИМХО.


    1. seriyPS
      12.08.2019 14:05

      Это TLSv1.3.


      1. blind_oracle
        12.08.2019 14:15

        Ну так Телеграм то мимикрирует под 1.2, а не 1.3.
        И если они будут мимикрировать под 1.3 то все равно сертификат нужен будет, просто он будет в том же пакете что и ServerHello.

        А если сертификата нет, то для любой вменяемой DPI это знак что надо этот траффик роутить в blackhole.


        1. seriyPS
          12.08.2019 14:16

          Нет, телеграм маскируется под TLS 1.3


          1. blind_oracle
            12.08.2019 14:33

            Откуда такая уверенность?
            Для TLSv1.3 он в ClientHello должен посылать 0x0304 в расширении supported_versions, а сервер отвечать тем же. И Wireshark это детектит. В коде прокси я нашел только 0x0303 т.е. TLSv1.2


            1. seriyPS
              12.08.2019 14:57

              https://tools.ietf.org/html/rfc8446#section-4.1.2


              legacy_version: In previous versions of TLS, this field was used for
              version negotiation and represented the highest version number
              supported by the client. Experience has shown that many servers
              do not properly implement version negotiation, leading to "version
              intolerance" in which the server rejects an otherwise acceptable
              ClientHello with a version number higher than it supports. In
              TLS 1.3, the client indicates its version preferences in the
              "supported_versions" extension (Section 4.2.1) and the
              legacy_version field MUST be set to 0x0303, which is the version
              number for TLS 1.2. TLS 1.3 ClientHellos are identified as having
              a legacy_version of 0x0303 and a supported_versions extension
              present with 0x0304 as the highest version indicated therein.
              (See Appendix D for details about backward compatibility.)


              1. blind_oracle
                12.08.2019 15:16

                Я читал RFC. И поэтому еще раз могу сказать — в коде Proxy, которое описывается в статье:
                1) нет 0x0304 в supported_versions
                2) нет сертификата

                Поэтому, как мне кажется, полезность данной обфускации невелика. Вот Websockets с реальным сертификатом было бы полезнее гораздо.


                1. alexbers Автор
                  12.08.2019 15:32

                  Константа 0x0304 есть в коде клиента т.к. именно клиент отсылает пакет с supported_versions.


                  1. blind_oracle
                    12.08.2019 15:53

                    В клиенте есть, но прокси тоже должен ответить 0x0304 чтобы сессия выглядела валидной. Плюс, опять же, сертификат должен быть чем-то хотя-бы декодируемым в X.509. Без этого сессия для DPI будет выглядеть не такой, как 99.99% других.

                    Как по мне — было бы идеально добавить способ использовать обычное TLS туннелирование (наверное с обмазкой всякими ALPN и прочим h2 чтобы выглядеть как HTTPS) и гонять MTProto уже внутри без модификаций.

                    Таким образом можно было бы поднять прокси на валидном TLS сертификате и полностью удовлетворять TLS-спеке если глядеть снаружи…


                    1. seriyPS
                      13.08.2019 00:36

                      В клиенте есть, но прокси тоже должен ответить 0x0304 чтобы сессия выглядела валидной. Плюс, опять же, сертификат должен быть чем-то хотя-бы декодируемым в X.509.

                      Не знаю, запустил wireshark, дернул страничку своего сайта через TLSv1.3 + HTTP/2 — пакеты такие же, как у телеграма. Нет в ServerHello никаких 0x0304, нет X.509 сертификатов.


                      Наверное если активно пытаться по https подключиться, то разница будет заметна. Но в пассивном режиме не вижу разницы.


                      1. blind_oracle
                        13.08.2019 02:50

                        Нет в ServerHello никаких 0x0304
                        Вот кусок ответа cloudflare.com:



                        нет X.509 сертификатов.
                        Т.е. вы считаете что сертификаты не участвуют в TLS 1.3 хендшейке? :) На самом деле Wireshark, судя по всему, еще не особо умеет 1.3 разбирать. Если глянете на ServerHello там большая часть пакета вообще никак не помечена — там сертификат и едет.


                        1. seriyPS
                          13.08.2019 16:32

                          Хм, да, выправы! Спасибо!


                          Поправил в Erlang версии: https://github.com/seriyps/mtproto_proxy/commit/5e601bce3e19cdc3d3f2b77313e3ee57b8dce482


                          image


                          1. blind_oracle
                            13.08.2019 16:41

                            Супер! Вот я теперь про сертификат думаю… У телеграмма ServerHello + CCS на вашем скрине 290 байт, а в реальном хендшейке там сзади еще Certificate Chain и еще что-то едет и пакет почти всегда в MTU упирается т.е. больше 1.5кб

                            Возможно имеет смысл реализовать что-то вроде:
                            1. Берем тот домен, который указан в SNI. Сейчас, как я понял, в клиенте захардкожен google.com
                            2. Резолвим, лезем туда по TLS на 443 порт
                            3. Тащим оттуда цепочку сертификатов
                            4. Кешируем
                            5. Отдаем ее всем клиентам в хендшейке
                            6. Повторяем пункты 1-4 в бэкграунде раз в Х минут


                            1. seriyPS
                              13.08.2019 16:49

                              как я понимаю CertificateСhain едет в ApplicationData фрейме? У меня, как и в python прокси, первый ApplicationData фрейм генерится как случайные данные случайной длины от 0 до 256:
                              https://github.com/seriyps/mtproto_proxy/blob/5e601bce3e19cdc3d3f2b77313e3ee57b8dce482/src/mtp_fake_tls.erl#L109
                              https://github.com/alexbers/mtprotoproxy/blob/e43ae9991102c8471c7f2436df63f7d64906940c/mtprotoproxy.py#L839
                              Возможно стоит увеличить размер?


                              Как я понимаю, засовывать туда реальный сертификат смысла большого нет, т.к. он зашифрован и пассивным анализом трафика его не расшифровать?


                              1. blind_oracle
                                13.08.2019 17:06

                                Да, согласен, я как-то пропустил что в 1.3 начали сообщение Certificate шифровать. Поэтому и Wireshark его не выделяет теперь.

                                Если там нет какой-либо структуры т.е. это просто бинарный блоб который парсится только после дешифровки (а так оно скорее всего и есть, но не читал пока), то думаю достаточно генерировать побольше мусора чтобы добить размер пакета до 1.5кб и в таком случае будет неотличимо от «средней» реальной сессии с цепочкой сертификатов из 2-3 штук.


                                1. alexbers Автор
                                  15.08.2019 14:56

                                  Спасибо за отличные мысли. Воплотил их в коде. Теперь прокси-сервер маскируется как tls 1.3 и отдаёт 1024-4096 случайных байт под видом зашифрованного сертификата.


                                  1. TheChief5055
                                    15.08.2019 15:01

                                    И DPI мгновенно определит это опросом, в том случае, когда его пробросит на «реальный https» (если это настроено). Просто разницей в длине пакетов. :(

                                    Сходу правило — «блокировать сайт, где TLS-соединения с разных клиентов дают существенно разную длину ApplicationData первого TLS-фрейма». Оно будет даже не слишком тяжёлым для DPI и не потребует активного сканирования https/tls DPI-агентом.


                                  1. blind_oracle
                                    15.08.2019 15:17

                                    Большое спасибо, отличный прокси!

                                    Следующим шагом, возможно, полезно было бы периодически дергать google.com и получать от него цепочку сертификатов — достаточно просто ее длины в зашифрованном виде. Пока в клиентах захардкожен Гугл, то можно только его юзать, а дальше по ситуации.

                                    И отдавать клиентам не рандом в диапазоне 1..4kb а именно эту длину. Так будет еще правдоподобнее :)

                                    Но, возможно, это уже оверкилл. Хотя реализовать не сложно.


                                    1. TheChief5055
                                      15.08.2019 15:30

                                      в клиентах захардкожен Гугл

                                      Тут одно «но»: отдать невалидному клиенту MASK_HOST:MASK_PORT с захардкоженным google.com не получится — браузер заругается, что хост, на который пришли, и домен в полученном сертификате не совпадают.


                                      1. alexbers Автор
                                        15.08.2019 15:43

                                        Смотря как заходить браузером. Если заходить по ip-адресу или по другому имени, то заругается, а если по правильному имени, то не заругается. Самый простой способ зайти браузером "правильно" — прописать google.com в /etc/hosts или аналогичный файл.


                                        Аналогично ведёт себя и настоящий google.com.


                                        1. TheChief5055
                                          15.08.2019 15:49

                                          если по правильному имени, то не заругается

                                          Это надо в hosts ковыряться, а активному агенту-сканеру РКН вы в hosts не залезете. :) Лучше уж ip своей прокси в любом dynamic dns сервисе зарегистировать и поставить его в TLS_DOMAIN. Всё красиво, все довольны.


                                      1. blind_oracle
                                        15.08.2019 15:48

                                        Не, тут речь именно про анализ трафика «снаружи» с помощью DPI и как можно сделать сессию аналогичную настоящей.

                                        Защита от каких-либо пробников, которые не зная секрета сканят адрес в поисках Telegram прокси — это ортогональная задача.

                                        Получив первый пакет от клиента (ClientHello) мы уже понимаем знает он секрет или нет. И если он знает, то отвечаем телеграмным ServerHello с прилепленным рандомом размером с реальный Certificate Chain гугля.

                                        А если нет, то проксируем на MASK_HOST:MASK_PORT прозрачно (это и так делается)


                                        1. TheChief5055
                                          15.08.2019 15:58

                                          Я как раз о том, что: а) реальный CertChain проксируемого сайта ещё надо узнать (хотя вот автор даже не против это внедрить) — раз; б) DPI (не знающий секрета) постучался к нам на проксю по ip адресу, его прокинуло на гугль, он получил сертификат гугля, посмотрел на имена хостов в сертификате, разрезолвил все — вашего ip там нет. Добро пожаловать в бан. Собственно, он может сделать это и раньше — смотреть на SNI в запросах и сравнивая dest ip с результатами резолвинга домена из SNI. ДПвБ.

                                          Единственный способ избежать этого: a) не подделывать tls_domain, а иметь настоящий, правильно ресолвящийся в ip нашего хоста; б) проксировать сайт, отдающий либо правильный сертификат с правильным хостнеймом, либо самогенерированный, но всё равно с правильным хостнеймом.


                                          1. blind_oracle
                                            15.08.2019 16:02

                                            а) Это не проблема
                                            б) DPI не стучится никуда, он зеркало траффика анализирует :) Но суть понятна, стучится некая проба.

                                            Так вот, вы сертификат который google.com отдает видели? Там в subjectAltName наверное сотня вайлдкардов вида *.google.com, *.google.co.uk и так далее. Как проба их резолвить будет? :)

                                            Ну даже если бы это было возможно — Гугл работает через anycast и прочий GeoDNS и в зависимости откуда отправлен запрос — IP будут совсем разные. Причем они еще могут ротироваться из сотни вариантов отдавая в DNS случайные N штук.


                                            1. TheChief5055
                                              15.08.2019 16:06

                                              Но суть понятна, стучится некая проба

                                              Да. И они уже давно работают. Отложенное сканирование целевых хостов по логам подозрительной активности делается.

                                              сотня вайлдкардов

                                              Я понял суть вашего посыла. :) Но редиректить на гугл — это всё равно как-то не по фэн-шую. Хотя, может, я и слишком заморачиваюсь.


                                            1. TheChief5055
                                              15.08.2019 16:29

                                              он зеркало траффика анализирует

                                              Как я уже писал, достаточно проверить, что хост отвечает при установлении TLS1.3-соединения сильно рандомными по длине ApplicationData (хранящим CertChain).


                                          1. blind_oracle
                                            15.08.2019 16:43

                                            б) проксировать сайт, отдающий либо правильный сертификат с правильным хостнеймом, либо самогенерированный, но всё равно с правильным хостнеймом.
                                            Проблема в том что в данный момент телеграм шлет google.com. Если они это изменят и будут в SNI отправлять hostname прокси — тогда да.


                                            1. TheChief5055
                                              15.08.2019 17:55

                                              Нет, не шлёт. Получив ссылку https://t.me/proxy?server=ip&port=443&secret=eesecret[hex:hostname], где к секрету дописано ещё и имя хоста (TLS_DOMAIN в терминах автора прокси) и запомнив её, телега отправляет в запросе совершенно нормальный SNI с именем хоста из ссылки.

                                              Я только что проверил это в wireshark.


                                              1. blind_oracle
                                                15.08.2019 18:44

                                                Ну, если дописать — то хорошо. Только по дефолту прокси генерирует ссылки в обычном формате без хостнейма и 99% людей ИМХО пользуются ими.

                                                А так, конечно, хорошо — осталось проверять размер certchain у хоста в MASK_HOST и отдавать такую же длину.


                                                1. TheChief5055
                                                  15.08.2019 19:09

                                                  Только по дефолту прокси генерирует ссылки в обычном формате без хостнейма

                                                  Если не указан TLS_DOMAIN — да. Но в нём-то как раз самая мякотка. :)


                              1. TheChief5055
                                13.08.2019 21:30
                                +1

                                Я бы с удовольствием отдавал точно ту cert chain, которую отдаёт мой Апач, который показывает РКН не знающим секрета юзерам котиков. Там честный-благородный LE, отчего ж не отдать. И выглядеть будет при возможном скане абсолютно нормально.


                                1. alexbers Автор
                                  15.08.2019 15:38

                                  Думаю, стоит периодически опрашивать https-сервер за ним и сохранять ответы на handshake (extensions и сертификат). Причём делать несколько запросов, чтобы понять какие части extensions случайные, а какие — нет. Возможно добавлю и это тоже


                                  1. TheChief5055
                                    15.08.2019 15:45

                                    Просто отлично! Ждём с нетерпением.


  1. iroln
    12.08.2019 14:40

    Вместо PyCrypto лучше использовать пакет cryptography. PyCrypto заброшен и много лет не поддерживается.


    Хорошо было бы сделать нормальный python-пакет, добавить setup.py файл и указать зависимости, для возможности установки вашего пакета стандартными средствами, например, в виртуальное окружение.


    1. alexbers Автор
      12.08.2019 14:51

      Прокси поддерживает сразу несколько библиотек:


      1. Cryptography
      2. Если Cryptography не найден, то PyCryptodome или PyCrypto
      3. Если ничего не найдено, то испольуется медленная реализация криптографии на Python

      Python-пакет есть: https://pypi.org/project/mtprotoproxy/, правда там стабильная версия, в которой ещё нет экспериментальной поддержки TLS.


      Ещё есть образ на докерхабе: https://hub.docker.com/r/alexbers/mtprotoproxy


      1. iroln
        12.08.2019 15:46

        А как вы сделали пакет на pypi? В репозитории не вижу ничего для packaging (setup.py, pyproject.toml, etc).


        Я пока просто взял последнюю версию из исходников и запускаю через сервис systemd. Не люблю я докеры-шмокеры для таких простых вещей. :)


        1. alexbers Автор
          12.08.2019 19:44

          Он в параллельной ветке: https://github.com/alexbers/mtprotoproxy/tree/pypi.
          С докером хорошо в том плане, что можно зафиксировать environment: версию Python и библиотек. Другие способы хороши, но поддерживать пользователей чуть сложнее, приходится спрашивать какая у них ОС, как они запускают и т.д. Это позволяет поддерживать пользователей бесплатно, не тратя на это много свободного времени.


          1. TheChief5055
            13.08.2019 14:22

            У меня и основная ветка отлично работает под pypy3. Аж с песнями. Стоит пакет cryptography и тоже без проблем работает.


            1. alexbers Автор
              13.08.2019 14:35

              Кстати можно несколько прокси-серверов на одном и том же порту запускать если нужна суперскорость. В этом случае, если машина многоядерная, будут использоваться все ядра. А если много оперативной памяти то можно размеры буферов увеличить, TO_CLT_BUFSIZE и TO_TG_BUFSIZE в конфиге.


      1. iroln
        12.08.2019 17:00
        +1

        С PyCrypto, кажется, не работает нововведение с TLS. Сервер валится если использовать expiremental-ключ:


        ...
        mtprotoproxy/mtprotoproxy.py", line 430, in read
            return self.decryptor.decrypt(data)
        /lib/python3.5/site-packages/Crypto
            return self._cipher.decrypt(ciphertext)
        TypeError: argument must be read-only bytes-like object, not bytearray

        С cryptography получилось подключиться с Android-клиента.


        Зачем вообще тянуть поддержку чего-либо кроме cryptography? PyCrypto заброшен, ваша реализация медленная. Зачем это нужно, если есть нормальная библиотека, которая активно поддерживается?


        1. blind_oracle
          12.08.2019 18:08

          А каким клиентом на Андроиде подключались? Официальный и его бета у меня чего-то не шмогли.


          1. iroln
            12.08.2019 18:28

            Официальная последняя версия v5.10.0 (1684).
            Посмотрите в логи, возможно, у вас на сервере тоже exception происходит.


            1. blind_oracle
              12.08.2019 19:47

              Неа, не происходит. И у меня cryptography установлен, так что должен работать по идее. Дамп траффика не смотрел пока, но в логах тихо, а на телефоне — Connecting…


              1. Allineer
                12.08.2019 21:24

                Аналогично.
                cryptography установлен. клиент v5.10.0 (1684)


            1. blind_oracle
              12.08.2019 20:31

              А ключ какой юзался? Тот который прокси выводит в stdout при старте с ремаркой experimental?


              1. iroln
                13.08.2019 07:27

                Да, ключ experimental, в конфиге TLS_ONLY = False. На двух андроид-устройствах попробовал, подключается и работает.


                1. blind_oracle
                  13.08.2019 10:02

                  Странно. Всё то же самое, конфиг:

                  PORT = 1443
                  
                  USERS = {
                      "tg":  "xxx",
                  }
                  
                  SECURE_ONLY = True
                  


                  Пробовал и без SECURE_ONLY, ничего не меняется. Проксю последнюю из GIT вытянул.


                  1. Allineer
                    13.08.2019 12:04

                    cryptography обновлен?


                    1. blind_oracle
                      13.08.2019 12:12

                      Из репы Ubuntu 18, возможно старый конечно. Попробую из pip дернуть.


                      1. iroln
                        13.08.2019 12:52

                        Не пользуюсь вообще пакетами из репы дистра. Всегда venv и те версии пакетов, какие нужны, либо самые последние. Так всегда надёжнее, удобнее и проще.


                        1. blind_oracle
                          13.08.2019 13:06

                          После установки cryptography из pip внутри venv все заработало, спасибо!

                          Насчет надежнее — да, скорее всего. Удобнее и проще — врядли. Тот же cryptography входит в базовую ОС и городить только ради него venv было не охота.


        1. alexbers Автор
          12.08.2019 19:48

          Советую использовать PyCryptodome или cryptography,
          PyCrypto заброшен, как вы верно сказали. Думаю дропнуть его поддержку.


        1. alexbers Автор
          13.08.2019 13:32

          Устранил вероятную причину такого поведения. Проверьте, пожалуйста, что стало лучше.


  1. tyderh
    12.08.2019 14:50

    России пока не научились, но закон уже приняли.

    Вообще нет, на yota невозможно пользоваться mtproto-проксями как раз из-за этого.


    1. alexbers Автор
      12.08.2019 15:12

      Да, вы правы, отдельные провайдеры экспериментируют с этим, но массово, как в Иране или Китае пока не блокируют по протоколу.


    1. ne_kotin
      12.08.2019 15:46

      на yota невозможно пользоваться mtproto-проксями

      не испытывал затруднений на Yota с MTProxy в dd-режиме. Хотя шейпить и детектить трафик они пытаются, да


      1. tyderh
        12.08.2019 15:47

        Картинки не загружается, работает очень медленно. Что с dd, что без него.


    1. aleki
      12.08.2019 18:15

      В качестве костыля для Yota можно использовать порт OpenVPN'а (1194) или порт WireGuard'а (51820) в конфиге MTProxy.


  1. achekalin
    12.08.2019 15:25

    При запуске прокси на Python вижу приписку в конце:
    The default secret 0123456789abcdef0123456789abcdef is used, this is not recommended
    На это обращать внимание?


    1. shifttstas
      12.08.2019 15:41

      Конечно, поменяйте секрет на свой, сейчас там 1234...


    1. iroln
      12.08.2019 15:44

      Сгенерируйте свой собственный секрет и измените в конфиге. Генерировать так, например:


      openssl rand -hex 16


    1. alexbers Автор
      12.08.2019 19:50

      Это значит, что обнаружить прокси автоматическим сканированием с перебором секретов будет легче. Лучше использовать уникальный секрет.


      1. blind_oracle
        13.08.2019 11:23

        Может лучше форсить пользователя задать какой-то секрет, либо генерировать при запуске рандомный? А то многие пойдут по пути наименьшего сопротивления…


        1. alexbers Автор
          13.08.2019 22:48

          Пока не придумал хорошего решения для этого. С одной стороны хочется чтобы прокси как можно проще поднимался, с другой, чтобы форсились рекомендованные настройки.


  1. divanus
    13.08.2019 09:07

    Telegram, это лучшее, что случилось с 1996 года с интернетом (по личным наблюдениям)


  1. rvs2016
    13.08.2019 11:38

    Какая программа показывает отображённую на первой картине статьи информацию: image


    1. Allineer
      13.08.2019 12:09

      А вы не из роскомнадзора?



  1. Allineer
    13.08.2019 12:07

    [мимо]