Для использования этой возможности добавили новый префикс секрета — «ee». Кроме того, добавили возможность кодировать секрет в адресе прокси сервера как base64, в дополнение к hex.
Перед тем, как углубиться в детали, попробуем разобраться как развивалась поддержка прокси-серверов в Telegram.
Предыстория
- Сначала Телеграм поддерживал SOCKS-прокси. Это помогало обойти блокировку серверов по IP, но протокол был заметен в трафике, а пароль передавался в открытом виде
- Примерно год назад выпустили официальный прокси, работающий по новому протоколу MTProto. В отличие от SOCKS, пароль в MTProto не передавался в открытом виде. В протоколе избавились от каких-либо служебных заголовков, по которым можно было бы понять что это действительно он. Ещё добавили возможность показа рекламы пользователям прокси-сервера
- Оказалось что прокси-серверы, работающие по протоколу MTProto, можно обнаружить по длине пакетов. При установке соединения клиент и прокси сервер обмениваются пакетами определённой длины, а при работе — пакетами одной и той же длины по модулю 4. Эта особенность начала использоваться крупными провайдерами для блокировки мессенжера. Разработчики Telegram отреагировали на это модификацией протокола, добавив в каждый пакет некоторое количество случайных байт. Так как изменение ломало совместимость, пришлось дополнить формат секрета специальным префиксом «dd», означающим использование модифицированного протокола:
tg://proxy?server=178.62.232.110&port=3256&secret=dd00000000000000000000000000000000
- При изучении особенностей блокировок прокси серверов в Китае и Иране выяснилось, что надзорные органы используют для детекта replay-атаки. В альтернативных реализациях прокси-серверов на Python, Erlang и Go появилась частичная защита от такого вида атак. Для этого прокси серверы запоминают данные, передающиеся на начальном этапе установки соединения и не дают повторно соединиться с такими же данными. У подхода есть проблема с крупными прокси-серверами, т.к. для запоминания требуется большое количество оперативной памяти
- В Китае и Иране применяют следующую тактику: если протокол неизвестен, то на всякий случай скорость его работы сильно режут. На практике это означает возможность использования 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)
vp7
12.08.2019 09:53На самом деле не хватает последнего шага:
- Возможность серверу определять домен по SNI и если домен не попадает в заданный список, то пропускать коннект дальше (на обычный web сервер с заглушкой).
- Возможность работы через WebSocket'ы, работая в качестве backend'а для обычного web server'а типа nginx, при этом добавив минимальную http basic аутентификацию.
Тогда прокси можно будет вешать на реальных живых сайтах, с реальным трафиком. Просто будет поддомен stat.mysite.ru или даже просто конкретный URL на сайте, закрытый http basic auth'ом.
Даже если "постучался" — тебя послали нафиг, а владелец сайта (ну если вдруг ему начнут задавать вопросы) честно скажет "это закрытый раздел сайта".
Ну а телега сможет сделать upgrade до вебсокетов и дальше кидать свой трафик, к примеру завернув туда тот самый mtproxy.
Тот же nginx в качестве фронтенда великолепно подойдёт.
И нет необходимости прикрываться именами типа google.com, наоборот веселее будет кому-то получить прокси на реальных серверах с реальным доменным именем типа bstat.gosuslugi.ru, закрывшись самым настоящим сертификатом *.gosuslugi.ru ;)))istepan
12.08.2019 10:04Лучше даже просто кидать 504 Gateway Timeout по прошествии нескольких минут. Без соединения по факту.
Первый плюс — у себя сэкономим трафик и ресурсы.
Второе, — займем ресурсы проверяющего бота.
alexbers Автор
12.08.2019 10:16+1- Тут даже можно пойти дальше — сделать один домен и если HMAC из поля random handshake-пакета не валидный, то редиректить. Сегодня вечером будет версия, которая так делает.
- Насколько я понял, тут нужна поддержка со стороны фронта или клиента Telegram. Для себя можно поднимать веб-версии Telegram, но маловероятно, что сторонний человек согласиться ввести свои credentials на сайт, не связанный с тг.
vp7
12.08.2019 10:32Обязательно нужна поддержка со стороны клиентов, причём тех, которые лежат в google/apple market'ах.
Как минимум — возможность работы через WebSocket.
Но если это реализовать, то блокировать телегу можно будет только тем самым "законом о суверенном интернете" с полной изоляцией российского сегмента сети. И то не факт ;)
navion
12.08.2019 10:14-2Это всё замечательно, но не спасёт от цензуры техногигантов: Telegram на iOS фильтрует некоторые каналы по указке калифорнийский леваков, хотя до этого блокировал лишь террористов, ЦП и за многократные нарушения DMCA.
shifttstas
12.08.2019 10:16-1Вам никто не мешает использовать не родной клиент или официальную веб-версию.
AEP
12.08.2019 15:34Официальная веб-версия, когда я ее последний раз проверял, поддерживала только текстовые чаты и вставку картинок, но не поддерживала звонки.
shifttstas
12.08.2019 15:40Так а что ещё нужно для просмотра заблокированного контента на платформе? Для звонков и всего — основной клиент, для заблокированного — веб версия.
ne_kotin
12.08.2019 12:59+1На android не фильтрует же? Пересаживайтесь на android, какие проблемы?
AibekAS
13.08.2019 08:52С фильтрацией все интересно)
Место действия Казахстан.
У меня, фильтруются всякие 18+ стикеры, каналы и они мне не доступны. Но моим друзьям, у которых я тестил акки, им все было доступно.
Foorya
13.08.2019 12:14Узбекистан, любая версия Телеграм (андроид, iOS и даже веб) блокирует 18+ каналы (якобы забанены за порнографию)
Revertis
12.08.2019 11:56Так а поставить MtProtoProxy за nginx'ом с валидными сайтами можно?
Я пробовал когда-то с помощью SSLH, но тогда сайты видели все коннекты клиентов как от 127.0.0.1.AlxDr
12.08.2019 12:37+1У SSLH есть «прозрачный режим», но его настройка несколько геморройна.
Если пока отказаться от этого FakeTLS, то средствами nginx можно проверять версию TLS (например) и неизвестные версии отправлять на МТ-прокси. В этом случае снаружи будет виден порт 443, на него можно будет соединиться стандартным клиентом и увидеть веб-сайт, но соединения от телеграм-клиентов туда же будут видны как неведомая хрень. В теории это не должно быть основанием для блока, так как такой подход используется для OpenVPN и других задач, на практике же непонятно.Revertis
12.08.2019 14:06Хм, а это интересно. Но что-то не получается. Вы ведь имели ввиду ssl_preread_protocol?
AlxDr
12.08.2019 15:08Да.
Вероятно, nginx собран без модуля ngx_stream_ssl_preread_module?
В пакетах по-умолчанию его почему-то нет, надо самому собирать.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'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 всякая байдистика и мтпрото там тоже есть — полёт нормальный :)Revertis
12.08.2019 15:38А вебсервер на порту, отличном от 443?
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 или ещё чему-нибудь.
blind_oracle
12.08.2019 13:45В хендшейке нет отправки сертификата сервером. И дальнейших шагов. DPI легко может блочить такую сессию как невалидную ИМХО.
seriyPS
12.08.2019 14:05Это TLSv1.3.
blind_oracle
12.08.2019 14:15Ну так Телеграм то мимикрирует под 1.2, а не 1.3.
И если они будут мимикрировать под 1.3 то все равно сертификат нужен будет, просто он будет в том же пакете что и ServerHello.
А если сертификата нет, то для любой вменяемой DPI это знак что надо этот траффик роутить в blackhole.seriyPS
12.08.2019 14:16Нет, телеграм маскируется под TLS 1.3
blind_oracle
12.08.2019 14:33Откуда такая уверенность?
Для TLSv1.3 он в ClientHello должен посылать 0x0304 в расширении supported_versions, а сервер отвечать тем же. И Wireshark это детектит. В коде прокси я нашел только 0x0303 т.е. TLSv1.2seriyPS
12.08.2019 14:57https://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.)blind_oracle
12.08.2019 15:16Я читал RFC. И поэтому еще раз могу сказать — в коде Proxy, которое описывается в статье:
1) нет 0x0304 в supported_versions
2) нет сертификата
Поэтому, как мне кажется, полезность данной обфускации невелика. Вот Websockets с реальным сертификатом было бы полезнее гораздо.alexbers Автор
12.08.2019 15:32Константа 0x0304 есть в коде клиента т.к. именно клиент отсылает пакет с supported_versions.
blind_oracle
12.08.2019 15:53В клиенте есть, но прокси тоже должен ответить 0x0304 чтобы сессия выглядела валидной. Плюс, опять же, сертификат должен быть чем-то хотя-бы декодируемым в X.509. Без этого сессия для DPI будет выглядеть не такой, как 99.99% других.
Как по мне — было бы идеально добавить способ использовать обычное TLS туннелирование (наверное с обмазкой всякими ALPN и прочим h2 чтобы выглядеть как HTTPS) и гонять MTProto уже внутри без модификаций.
Таким образом можно было бы поднять прокси на валидном TLS сертификате и полностью удовлетворять TLS-спеке если глядеть снаружи…seriyPS
13.08.2019 00:36В клиенте есть, но прокси тоже должен ответить 0x0304 чтобы сессия выглядела валидной. Плюс, опять же, сертификат должен быть чем-то хотя-бы декодируемым в X.509.
Не знаю, запустил wireshark, дернул страничку своего сайта через TLSv1.3 + HTTP/2 — пакеты такие же, как у телеграма. Нет в ServerHello никаких 0x0304, нет X.509 сертификатов.
Наверное если активно пытаться по https подключиться, то разница будет заметна. Но в пассивном режиме не вижу разницы.
blind_oracle
13.08.2019 02:50Нет в ServerHello никаких 0x0304
Вот кусок ответа cloudflare.com:
нет X.509 сертификатов.
Т.е. вы считаете что сертификаты не участвуют в TLS 1.3 хендшейке? :) На самом деле Wireshark, судя по всему, еще не особо умеет 1.3 разбирать. Если глянете на ServerHello там большая часть пакета вообще никак не помечена — там сертификат и едет.seriyPS
13.08.2019 16:32Хм, да, выправы! Спасибо!
Поправил в Erlang версии: https://github.com/seriyps/mtproto_proxy/commit/5e601bce3e19cdc3d3f2b77313e3ee57b8dce482
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 в бэкграунде раз в Х минут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
Возможно стоит увеличить размер?
Как я понимаю, засовывать туда реальный сертификат смысла большого нет, т.к. он зашифрован и пассивным анализом трафика его не расшифровать?
blind_oracle
13.08.2019 17:06Да, согласен, я как-то пропустил что в 1.3 начали сообщение Certificate шифровать. Поэтому и Wireshark его не выделяет теперь.
Если там нет какой-либо структуры т.е. это просто бинарный блоб который парсится только после дешифровки (а так оно скорее всего и есть, но не читал пока), то думаю достаточно генерировать побольше мусора чтобы добить размер пакета до 1.5кб и в таком случае будет неотличимо от «средней» реальной сессии с цепочкой сертификатов из 2-3 штук.alexbers Автор
15.08.2019 14:56Спасибо за отличные мысли. Воплотил их в коде. Теперь прокси-сервер маскируется как tls 1.3 и отдаёт 1024-4096 случайных байт под видом зашифрованного сертификата.
TheChief5055
15.08.2019 15:01И DPI мгновенно определит это опросом, в том случае, когда его пробросит на «реальный https» (если это настроено). Просто разницей в длине пакетов. :(
Сходу правило — «блокировать сайт, где TLS-соединения с разных клиентов дают существенно разную длину ApplicationData первого TLS-фрейма». Оно будет даже не слишком тяжёлым для DPI и не потребует активного сканирования https/tls DPI-агентом.
blind_oracle
15.08.2019 15:17Большое спасибо, отличный прокси!
Следующим шагом, возможно, полезно было бы периодически дергать google.com и получать от него цепочку сертификатов — достаточно просто ее длины в зашифрованном виде. Пока в клиентах захардкожен Гугл, то можно только его юзать, а дальше по ситуации.
И отдавать клиентам не рандом в диапазоне 1..4kb а именно эту длину. Так будет еще правдоподобнее :)
Но, возможно, это уже оверкилл. Хотя реализовать не сложно.TheChief5055
15.08.2019 15:30в клиентах захардкожен Гугл
Тут одно «но»: отдать невалидному клиенту MASK_HOST:MASK_PORT с захардкоженным google.com не получится — браузер заругается, что хост, на который пришли, и домен в полученном сертификате не совпадают.alexbers Автор
15.08.2019 15:43Смотря как заходить браузером. Если заходить по ip-адресу или по другому имени, то заругается, а если по правильному имени, то не заругается. Самый простой способ зайти браузером "правильно" — прописать google.com в /etc/hosts или аналогичный файл.
Аналогично ведёт себя и настоящий google.com.
TheChief5055
15.08.2019 15:49если по правильному имени, то не заругается
Это надо в hosts ковыряться, а активному агенту-сканеру РКН вы в hosts не залезете. :) Лучше уж ip своей прокси в любом dynamic dns сервисе зарегистировать и поставить его в TLS_DOMAIN. Всё красиво, все довольны.
blind_oracle
15.08.2019 15:48Не, тут речь именно про анализ трафика «снаружи» с помощью DPI и как можно сделать сессию аналогичную настоящей.
Защита от каких-либо пробников, которые не зная секрета сканят адрес в поисках Telegram прокси — это ортогональная задача.
Получив первый пакет от клиента (ClientHello) мы уже понимаем знает он секрет или нет. И если он знает, то отвечаем телеграмным ServerHello с прилепленным рандомом размером с реальный Certificate Chain гугля.
А если нет, то проксируем на MASK_HOST:MASK_PORT прозрачно (это и так делается)TheChief5055
15.08.2019 15:58Я как раз о том, что: а) реальный CertChain проксируемого сайта ещё надо узнать (хотя вот автор даже не против это внедрить) — раз; б) DPI (не знающий секрета) постучался к нам на проксю по ip адресу, его прокинуло на гугль, он получил сертификат гугля, посмотрел на имена хостов в сертификате, разрезолвил все — вашего ip там нет. Добро пожаловать в бан. Собственно, он может сделать это и раньше — смотреть на SNI в запросах и сравнивая dest ip с результатами резолвинга домена из SNI. ДПвБ.
Единственный способ избежать этого: a) не подделывать tls_domain, а иметь настоящий, правильно ресолвящийся в ip нашего хоста; б) проксировать сайт, отдающий либо правильный сертификат с правильным хостнеймом, либо самогенерированный, но всё равно с правильным хостнеймом.blind_oracle
15.08.2019 16:02а) Это не проблема
б) DPI не стучится никуда, он зеркало траффика анализирует :) Но суть понятна, стучится некая проба.
Так вот, вы сертификат который google.com отдает видели? Там в subjectAltName наверное сотня вайлдкардов вида *.google.com, *.google.co.uk и так далее. Как проба их резолвить будет? :)
Ну даже если бы это было возможно — Гугл работает через anycast и прочий GeoDNS и в зависимости откуда отправлен запрос — IP будут совсем разные. Причем они еще могут ротироваться из сотни вариантов отдавая в DNS случайные N штук.TheChief5055
15.08.2019 16:06Но суть понятна, стучится некая проба
Да. И они уже давно работают. Отложенное сканирование целевых хостов по логам подозрительной активности делается.
сотня вайлдкардов
Я понял суть вашего посыла. :) Но редиректить на гугл — это всё равно как-то не по фэн-шую. Хотя, может, я и слишком заморачиваюсь.
TheChief5055
15.08.2019 16:29он зеркало траффика анализирует
Как я уже писал, достаточно проверить, что хост отвечает при установлении TLS1.3-соединения сильно рандомными по длине ApplicationData (хранящим CertChain).
blind_oracle
15.08.2019 16:43б) проксировать сайт, отдающий либо правильный сертификат с правильным хостнеймом, либо самогенерированный, но всё равно с правильным хостнеймом.
Проблема в том что в данный момент телеграм шлет google.com. Если они это изменят и будут в SNI отправлять hostname прокси — тогда да.TheChief5055
15.08.2019 17:55Нет, не шлёт. Получив ссылку
https://t.me/proxy?server=ip&port=443&secret=eesecret[hex:hostname]
, где к секрету дописано ещё и имя хоста (TLS_DOMAIN в терминах автора прокси) и запомнив её, телега отправляет в запросе совершенно нормальный SNI с именем хоста из ссылки.
Я только что проверил это в wireshark.blind_oracle
15.08.2019 18:44Ну, если дописать — то хорошо. Только по дефолту прокси генерирует ссылки в обычном формате без хостнейма и 99% людей ИМХО пользуются ими.
А так, конечно, хорошо — осталось проверять размер certchain у хоста в MASK_HOST и отдавать такую же длину.TheChief5055
15.08.2019 19:09Только по дефолту прокси генерирует ссылки в обычном формате без хостнейма
Если не указан TLS_DOMAIN — да. Но в нём-то как раз самая мякотка. :)
TheChief5055
13.08.2019 21:30+1Я бы с удовольствием отдавал точно ту cert chain, которую отдаёт мой Апач, который показывает
РКНне знающим секрета юзерам котиков. Там честный-благородный LE, отчего ж не отдать. И выглядеть будет при возможном скане абсолютно нормально.alexbers Автор
15.08.2019 15:38Думаю, стоит периодически опрашивать https-сервер за ним и сохранять ответы на handshake (extensions и сертификат). Причём делать несколько запросов, чтобы понять какие части extensions случайные, а какие — нет. Возможно добавлю и это тоже
iroln
12.08.2019 14:40Вместо PyCrypto лучше использовать пакет cryptography. PyCrypto заброшен и много лет не поддерживается.
Хорошо было бы сделать нормальный python-пакет, добавить
setup.py
файл и указать зависимости, для возможности установки вашего пакета стандартными средствами, например, в виртуальное окружение.alexbers Автор
12.08.2019 14:51Прокси поддерживает сразу несколько библиотек:
- Cryptography
- Если Cryptography не найден, то PyCryptodome или PyCrypto
- Если ничего не найдено, то испольуется медленная реализация криптографии на Python
Python-пакет есть: https://pypi.org/project/mtprotoproxy/, правда там стабильная версия, в которой ещё нет экспериментальной поддержки TLS.
Ещё есть образ на докерхабе: https://hub.docker.com/r/alexbers/mtprotoproxy
iroln
12.08.2019 15:46А как вы сделали пакет на pypi? В репозитории не вижу ничего для packaging (setup.py, pyproject.toml, etc).
Я пока просто взял последнюю версию из исходников и запускаю через сервис systemd. Не люблю я докеры-шмокеры для таких простых вещей. :)
alexbers Автор
12.08.2019 19:44Он в параллельной ветке: https://github.com/alexbers/mtprotoproxy/tree/pypi.
С докером хорошо в том плане, что можно зафиксировать environment: версию Python и библиотек. Другие способы хороши, но поддерживать пользователей чуть сложнее, приходится спрашивать какая у них ОС, как они запускают и т.д. Это позволяет поддерживать пользователей бесплатно, не тратя на это много свободного времени.TheChief5055
13.08.2019 14:22У меня и основная ветка отлично работает под pypy3. Аж с песнями. Стоит пакет cryptography и тоже без проблем работает.
alexbers Автор
13.08.2019 14:35Кстати можно несколько прокси-серверов на одном и том же порту запускать если нужна суперскорость. В этом случае, если машина многоядерная, будут использоваться все ядра. А если много оперативной памяти то можно размеры буферов увеличить, TO_CLT_BUFSIZE и TO_TG_BUFSIZE в конфиге.
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 заброшен, ваша реализация медленная. Зачем это нужно, если есть нормальная библиотека, которая активно поддерживается?
blind_oracle
12.08.2019 18:08А каким клиентом на Андроиде подключались? Официальный и его бета у меня чего-то не шмогли.
iroln
12.08.2019 18:28Официальная последняя версия v5.10.0 (1684).
Посмотрите в логи, возможно, у вас на сервере тоже exception происходит.blind_oracle
12.08.2019 19:47Неа, не происходит. И у меня cryptography установлен, так что должен работать по идее. Дамп траффика не смотрел пока, но в логах тихо, а на телефоне — Connecting…
blind_oracle
12.08.2019 20:31А ключ какой юзался? Тот который прокси выводит в stdout при старте с ремаркой experimental?
iroln
13.08.2019 07:27Да, ключ experimental, в конфиге
TLS_ONLY = False
. На двух андроид-устройствах попробовал, подключается и работает.blind_oracle
13.08.2019 10:02Странно. Всё то же самое, конфиг:
PORT = 1443 USERS = { "tg": "xxx", } SECURE_ONLY = True
Пробовал и без SECURE_ONLY, ничего не меняется. Проксю последнюю из GIT вытянул.Allineer
13.08.2019 12:04cryptography обновлен?
blind_oracle
13.08.2019 12:12Из репы Ubuntu 18, возможно старый конечно. Попробую из pip дернуть.
iroln
13.08.2019 12:52Не пользуюсь вообще пакетами из репы дистра. Всегда venv и те версии пакетов, какие нужны, либо самые последние. Так всегда надёжнее, удобнее и проще.
blind_oracle
13.08.2019 13:06После установки cryptography из pip внутри venv все заработало, спасибо!
Насчет надежнее — да, скорее всего. Удобнее и проще — врядли. Тот же cryptography входит в базовую ОС и городить только ради него venv было не охота.
alexbers Автор
12.08.2019 19:48Советую использовать PyCryptodome или cryptography,
PyCrypto заброшен, как вы верно сказали. Думаю дропнуть его поддержку.
alexbers Автор
13.08.2019 13:32Устранил вероятную причину такого поведения. Проверьте, пожалуйста, что стало лучше.
tyderh
12.08.2019 14:50России пока не научились, но закон уже приняли.
Вообще нет, на yota невозможно пользоваться mtproto-проксями как раз из-за этого.
alexbers Автор
12.08.2019 15:12Да, вы правы, отдельные провайдеры экспериментируют с этим, но массово, как в Иране или Китае пока не блокируют по протоколу.
aleki
12.08.2019 18:15В качестве костыля для Yota можно использовать порт OpenVPN'а (1194) или порт WireGuard'а (51820) в конфиге MTProxy.
achekalin
12.08.2019 15:25При запуске прокси на Python вижу приписку в конце:
The default secret 0123456789abcdef0123456789abcdef is used, this is not recommended
На это обращать внимание?iroln
12.08.2019 15:44Сгенерируйте свой собственный секрет и измените в конфиге. Генерировать так, например:
openssl rand -hex 16
alexbers Автор
12.08.2019 19:50Это значит, что обнаружить прокси автоматическим сканированием с перебором секретов будет легче. Лучше использовать уникальный секрет.
blind_oracle
13.08.2019 11:23Может лучше форсить пользователя задать какой-то секрет, либо генерировать при запуске рандомный? А то многие пойдут по пути наименьшего сопротивления…
alexbers Автор
13.08.2019 22:48Пока не придумал хорошего решения для этого. С одной стороны хочется чтобы прокси как можно проще поднимался, с другой, чтобы форсились рекомендованные настройки.
divanus
13.08.2019 09:07Telegram, это лучшее, что случилось с 1996 года с интернетом (по личным наблюдениям)
zzzmmtt
Я просто оставлю это здесь.
shifttstas
Этот пост так же хороший и приводит новые технические подробности.
PS: если я правильно понял, автор поста — автор прокси на Python который один из первых и добавил поддержку.
alexbers Автор
Да, всё так. Написал эту статью параллельно с добавлением поддержки, но, видя как авторы телеграм маскировали её в коммитах бета-версий, решил подождать релиза, чтобы не дать создателям средств DPI времени подготовиться.
shifttstas
Не планируете реализовать «слив» трафика с другим SNI на другой порт?
Т.е когда к вам идёт запрос не на домен который указан в прокси а на другой — показывать обычный сайт.
alexbers Автор
Как раз экспериментирую с этим.
Whuthering
Я бы сделал чюдаже по-другому — если после расшифровки TLS прокси видит, что это валидный телеграм-коннект, то обрабатывать его как надо, а если нет — пересылать туда-сюда на порт локального веб-сервера, где котики и зеркало Википедии. Так сказать, защита от https probing.
shifttstas
Это фактически нужно проверять ключ, если он не подошёл — отправляем в порт веб сервера. И да — это самый лучший вариант. Допускаю, что для этого придётся использовать реальный домен + letsencrypt для автоматизации.
Whuthering
SNI-имена в пакете не криптуются (вроде в TLS1.3 завезти обещали только), поэтому для DPI это не будет проблемой. Так что польща будет разве что только из-за сильного желания пошарить 443 порт на белом айпи… :)
shifttstas
Ну так со временем будет eSNI и это будет иметь смысл.
AlxDr
Это и сейчас можно сделать разными способами, используя сторонний мультиплексор типа SSLH или даже средствами nginx.
Но было бы гораздо практичнее, особенно для чайников, если бы это было реализовано на уровне прокси. Да и обнаружение было бы гораздо сложнее, сейчас оно всё-таки возможно, если система контроля активная и сама пощупает порт.
shifttstas
Нет нельзя, сейчас домен А может использоваться только: либо телеграм либо веб-сервер.
Доработке прокси позволит использовать один и тот же домен А и для прокси и для веб-сервер. Отправляя запросы на последний в случае ошибки в секрете.
AlxDr
Да, это наиболее верный подход, как по мне.
inkvizitor68sl
Можно вообще использовать какой-то отдельный урл для проксирования. Вряд ли сканилка в https найдет что-то вроде domain.com/0aihs9iqwhrhiasdihfhoiaoihsf/
А на самом домене вешать уже нормальный сайт.
seriyPS
URL в TLS mtproto proxy протоколе не используется
inkvizitor68sl
Ну так самое время начать. Это же очевидное решение — сто лет назад люди вешали php-анонимайзеры на отдельный урл своего сайта. А теперь https и вовсе не позволит найти этот урл, если его хоть немного спрятать.
seriyPS
Ну для этого придётся полноценный TLS реализовывать. То что в mtproto proxy — хоть и маскируется под TLS 1.3, но внутри совсем по другому устроено.
inkvizitor68sl
Ну так это как раз и есть то место, где нужен полноценный https. Как DPI будут бороться с мессенджером, для поднятия прокси которому достаточно на любой свой сайт закинуть по какому-нибудь хитрому урлу php-файл? А если в клиент можно вбивать такие урлы сотнями?
Зачем нужен mtproto proxy, который прямо таки кричит в любой DPI помощнее «вот он я, берите меня, unknown proto!» — тоже неясно.
sumanai
Очевидно, чтобы вставлять рекламу. Сейчас это модно. Купив новый телефон, я офигел от количества рекламы изо всех щелей.
Ну и реклама типа должна стимулировать поднимать прокси.
TheChief5055
Почему? DPI никакого URL внутри TLS не увидит, как и не увидит того, что они не используются. Максимум — SNI (так никто не мешает указывать реальный dyndns-хост). DPI видит, что «на хост ходят по TLS» и всё. Расшифровать содержимое оно не может, не зная секрета. Попробует зайти на хост само — получит страничку с котиками (если в проксе настроен проброс невалидных коннектов на реальный веб-сервер).
Где я ошибаюсь, полагая, что у DPI нет критериев для вынесения предположения о содержимом TLS1.3 сессии?
inkvizitor68sl
> DPI никакого URL внутри TLS не увидит,
Зато увидит, что работающего TLS на втором конце нет.
TheChief5055
Я и прошу объяснить — КАК? По каким признакам?
inkvizitor68sl
Ткнётся активным сканером и посмотрит, что там.
Whuthering
AlxDr
Речь о том, что без FakeTLS пошарить порт на белом айпи таки можно :)
Впрочем, я думаю, что и с ним можно, если речь именно о белом айпи, а не домене — средствами nginx. Но смысла лично я мало вижу, подожду решения с проверкой ключа.
TheChief5055
SSLH не может различить «просто TLS» и «OpenVPN в „tls-crypt“ режиме». По крайней мере, у меня это не заработало ни на 1.18-1 из штатных реп, ни на самособранном 1.20.
AlxDr
SNI же виден при установке соединения TLS.
И поэтому использование какого-то особого SNI — сразу палево, разве нет?
Нужен мультиплексор по ключу, как описано ниже. Если ключ подходит, то соединять с прокси, если нет, то передавать на веб-сервер, который может обрабатывать трафик как угодно, у него могут быть несколько виртуальных доменов даже.
Телеграм-клиенты при этом должны как раз корректный SNI использовать, чтобы внешне не отличаться от стандартных веб-клиентов.
alexbers Автор
Закончил экспериментировать и закоммитил эту функциональность. Сейчас можно указывать хост и порт на который будет незаметно проксироваться трафик "плохих" клиентов.
Попробую продемонстрировать как это работает:
Это сделано для того, чтобы нельзя было задетектить прокси-сервер по особенностям работы с TLS.
Revertis
Вот это офигенно!
TheChief5055
Супер! И это у меня заработало в цепочке: openvpn (shared port) > mtprotoproxy > apache2. Апач, конечно же, показывает кошечек
и порно. Всё на стандартном 443. «Вы знаете? Я счастлив.» © Друпи.PS: мобильный Telegram (андроид) сходит с ума от сгенерированной и втянутой ссылки на прокси с более-менее длинным TLS_DOMAIN. И просто зависает.
alexbers Автор
Советую посмотреть это подробнее, вдруг там буфер переполняется и можно хакнуть.
TheChief5055
Возможно, но делать ревизию кода мне не по силам.
Я обратил внимание, что сгенерированный секрет с доменом google.com не содержит в конце символа суффикса «=». И с ним моб.клиент работает. А вот с моим доменом секрет имеет в конце суффикс «=» — и клиент ломается. Криво декодируется base64 в клиенте?
alexbers Автор
Можно проверить, декодировав в обратную сторону. В клиенте для ios есть бага с декодированием некоторых символов base64.
Allineer
В Telegram Desktop v.1.8.1 же вообще не удается вставить секрет с символами "=" или "%" — они просто не пропечатываются.
С моим доменом секрет получается в таком виде:
7u***************wjjNQRnYXRlLmtueXNob3YuaW5mbw%3D%3D
Кстати, а можно попросить объяснить, на что влияет значение TLS_DOMAIN в текущей реализации? Особенно учитывая, что можно произвольно изменить несколько последних символов секрета уже прописывая его в клиенте и тебе ничего за это не будет?
alexbers Автор
Сейчас ни на что, кроме генерации ссылки в начале работы. Планирую его использовать на серверах с большим количеством секретов, чтобы по имени можно сразу было бы пробовать расшифровать с нужным секретом.
В ближайший час планирую поменять вывод адреса прокси по умолчанию, чтобы не использовалось base64 пока авторы Telegram не починят клиента для ios.
alexbers Автор
Поменял код, чтобы в ссылках на прокси не использовался base64. Верну обратно когда пофиксят.
TheChief5055
Данная правка помогла также андроид-клиенту (ошибка, видимо, у них общая). Огромное спасибо!
Whuthering
У меня почему-то не работает.
Сервер стартует, пишет в консоль строку подключения.
Пытаюсь соединиться с клиента Android v5.10.0 (1684) — висит в «Connecting...» и всё. Процесс живой. Со старым протоколом (dd-) соединяется нормально. Если браузером попробовать зайти на IP и порт mtproxy, то показывается страница, куда стоит редирект с MASK_HOST и MASK_PORT в конфиге.
Cryptography пакет стоит
Upd. При этом десктопный клиент 1.8.1 через этот же прокси в tls-режиме работает без проблем. Мистика какая-то.
Whuthering
Всё заработало с последним коммитом, отключающим base64-кодирование в выводимой строке подключения.
TheChief5055
Подтверждаю, мобильный клиент заработал с hex-секретом.
Acuna
Почему Python был выбран для разработки? Он же медленнее той же Ноды в пять раз (Нода даже С++ уже уделывает в некоторых областях). Понятно что это потому-что его и учили, ибо он довольно новый, но зачем учить один из самых медленных по всем тестам языков?
alexbers Автор
На практике проблемы производительности не возникает. Даже самой дешёвой виртуальной машины хватает чтобы обслужить несколько тысяч пользователей. Но столько пользователей набрать тяжело т.к. основные каналы распространения информации о прокси серверах мониторятся. При большом числе пользователей прокси сервер может быть заблокирован по паттернам трафика
Acuna
Понятно, но проблемы производительности и на PHP не возникает, хотя говорят что тормозный, а он очень даже, до JS конечно не дотягивает, но все же. Вот я и спрашиваю, почему именно Python? По мне это как с парашютом прыгать — можно ноги сломать, причем неиллюзорно так, но я же не прыгаю там, где можно ноги сломать, я на травку прыгаю.
alexbers Автор
У меня больше всего опыта работы с этим языком. Но есть и другие реализации разной степени продвинутости: на go, erlang, си, c#, nodejs, java, pony и т.д.
Если нужна производительность, то лучше выбрать официальную реализацию на си. Её код всё ещё приводит меня в восторг. Правда, они уже пол года ничего не коммитили.
Acuna
Ого! Не знал что у него столько реализаций!) Ясно, спасибо, покопаюсь…
python273
лол
https://en.wikipedia.org/wiki/Python_(programming_language)
https://en.wikipedia.org/wiki/Node.js
https://en.wikipedia.org/wiki/JavaScript
Edit: добавил JS
Acuna
А, полагаю это и есть ответ на мой вопрос, давно его знают просто, вот и пишут) Просто как-то психологически не могу перестроиться на то, что это старый язык. Старыми считаются С, Java, да хоть PHP тот же, что уж там…
batyrmastyr
1) Нода не в пять раз быстрее Питона.
2) «В некоторых областях» бывает всякое: и Free Pascal быстрее Си и C++, и Нода в пять раз медленее Питона, и PHP отстаёт от Go и C++ на смешные 7-15%.
3) Нода память жрёт как не в себя.
4) Js один из худших языков среди массовых.
Acuna
Вы правы, не в пять, ибо часто даже в десять и выше
schors
Правильно, да :)