December 7, 2018

Обход авторизации публичного wifi используя DNS туннель

Дано: полное отсутствие интернета и виднеющийся WiFi hot-spot, в котором предлагают ввести логин-пароль. Или 3G, в котором нет интернета (потому что закончились деньги), но есть страничка провайдера с предложением дать оных денег.

Задача: получить интернет (легальным?) методом посредством туннелирования его через DNS.

Решение: linux+ iodine + routing + NAT + squid, и всё это под управлением network manager'а.

В статье: описание организации DNS туннеля посредсредством программы iodine, нюансы организации маршрутизации через образовавшийся туннель, самописный помощник для iodine и network manager.

Лирика: Занесла меня судьба на славный остров Кипр, кой славен своим П/пафосом, фраппэ и таким интернетом, после которого российские опсосы начинают выглядеть ангелами во плоти. В частности, попытка подключиться к интернетам закончилась ожиданием, что местный провайдер (Сyta) смилостивится, таки закончит пить оный фраппэ и дотянет до меня поганый ADSL 4Мб/768кбит всего-навсего за €151 (подключение) + €40 в месяц (за 4 мегабита! >_<). Ожидание тянулось и тянулось (как бы уже третья неделя пошла), а рядом был славный PrimeTel, который предлагал за €4/час (172р/час) осчастливить меня интернетом прямо тут и сейчас через едва видный WiFi. Я бы даже и согласился, но видна точка доступа была только на балконе, а в квартире связь была нестабильной и часто терялась. Так что оставалось одно решение (помимо взлома WEP-сети соседей, что уж совсем уголовщина) — это злоупотребить сервисом DNS, который безвозмездно, то есть даром, предоставляет PrimeTel для своих незарегистрировавшихся подлюченцев.

Те, кому интересно «howto» — решение далее по тексту, а пока что начнём с теории процесса.

DNS tunneling

Если у нас под руками есть работающий DNS-ресолвер (то есть сервер DNS с включенной рекурсией), то он может на наш запрос об IP-адресе узла foobar.example.com пойти на .com, узнать там адрес сервера, отвечающего на example.com, пойти на example.com, уточнить кто отвечает за foobar, и у указанного сервера уточнить подробности. Узнавать он может не только IP-адрес (A и AAAA записи), но и кучу другой информации, которая может храниться в DNS. В частности:

  • MX — содержит строку с описанием того, кому слать почту для foobar.example.com
  • SRV — содержит строку с информацией о том, какие (новые) сервисы есть и по каким адресам — например, её использует джаббер
  • TXT — случайная информация в текстовом виде. Чаще всего её на практике используют для SPF (правил, описывающих кто может отправлять почту с домена foobar.example.com).

(тут потерялась 300кб лекция о том, что такое DNS и как он работает).

Для наших нужд из этого вытекает простая вещь: мы можем попросить ресолвер узнать немного информации у foobar.example.com. Ресолвер пойдёт, узнает, закеширует (если мы снова запросим её, то он ответит из кеша) и отдаст нам. Если мы каждый раз будем запрашивать другую запись (для другого имени в этой зоне или другой тип записи), то ресолвер будет послушно ходить и ресолвить. Каждый раз. Заметим, для ускорения процесса он закеширует информацию «к кому ходить за такой-то информацией» и процесс в дальнейшем будет происходить в один хоп.

Ура, канал передачи информации от сервера к нам есть. А обратно? Но мы же можем попросить узнать у example.com не foobar.example.com, а, например,

0ahb282M-J2hbM->M-nYM-VgdM-OJM-
CM->M-nivlm4M-T5M-FM-p1M-t5M-
fM-uM-IvLM-HM-NM-aM-IM-eLAM-
BM-TM-qM-KM-UDM-NM-uM-]M-WM-
jM-DdbM->M-k.QVM-lM-uM-`v
[email protected]

(это один длиииный адрес сайта в интернетах, написанный столбиком для удобства неразрыва ваших френдлент)

Ресолвер пойдёт на example.com и спросит его об этом адресе. А если в адрес закодировано послание? (Криптоложцы встрепенулись — закодированное в имени послание!)

А если?.. Да, именно так. Имя содержит в себе зашифрованное послание. Мы нашли метод передачи информации на сервер. DNS-сервер. Или фальшивый сервер, который притворяется DNS-сервером, а на самом деле слушает зашифрованные в именах послания и отвечает на них зашифрованными посланиями в ответах на запросы.

Другими словами, мы получаем канал передачи данных на сервер и с сервера клиенту через DNS-ресолвер.

На этом принципе основывается работа утилиты iodine. В настоящий момент, по моим наблюдениям, это единственное реально работающее приложение с разумным объёмом настройки.

Настройка iodine

Настройка iodine сводится к трём важным этапам.

Подготовить доменную зону.

Запустить сервер с настройками.

Запустить клиента с настройками.

После этого нам придётся ещё научиться этим каналом пользоваться, но это уже следующая глава.

Итак, начнём со сложного — подготовка DNS.

Настройка в одном предложении: нам надо делегировать на будущий iodine-сервер доменную зону.

Настройка подробно:

Нам надо иметь доступ к администрированию доменного имени. Если домен делегирован на наш сервер — никаких проблем, мы можем прописать в бинде поддомен и делегировать его на iodine-сервер. Если же у нас только «админка провайдера» — то проблема куда более сложная — нужно каким-то образом прописать два поддомена, на одном из них указать NS, указывающую на второй поддомен, который, в свою очередь уже имеет A-запись.

Путано? Давайте ещё раз. Вот пример из моего конфига бинда:

i  IN  NS  j
j  IN  A   256.257.258.259

В нём написано: i.example.com имеет в качестве NS'а сервер j.example.com, который имеет IP-адрес 256.257.258.259.

На хабре есть статья о том, как решить проблему без контроля над доменным именем. Но для настоящего админа не иметь контроля хоть над каким-то доменным именем было бы странно, правда?

Вторая часть — это настройка сервера. Она довольно проста, хотя есть важная деталь: Нельзя запускать iodine на сервере, где работает обычный DNS-сервер. Причина проста — номер порта 53/UDP без права его поменять.

Запуск сервера (iodined) прост:

iodined 10.99.99.1/24 -c i.example.com (нужно указывать имя, для которого мы указали NS, а не имя с 'A' записью. — если этого не сделать, то ресолвер просто не дойдёт до нашего iodine). -c отключает проверку на IP. Я не вникал в подробности, но лучше иметь эту штуку работающей ДО того, как останешься один на один с чужим DNS'ом. Адрес в начале — это адрес в туннеле (tuntap), который будет иметь сервер. Клиенты будут получать следующие адреса (.2, .3 и т.д.).

Если запуск iodine скриптуется, то можно ещё добавить пароль: iodined -P SECRET 10.99.99.1/24 -c i.example.com. Запускать надо от root'а, чтобы iodined мог добавить туннель. В принципе, его можно добавить в rc.local, или просто запустить с & после имени.

Важно: версии iodine/iodined должны совпадать. Строго. Иначе связи не будет. Если на десктопе одна версия линукса, а на сервере другая (что типично) — лучше на сервер ставить версию с клиента. Просто скачать deb'ку с репозитория и поставить (благо там зависимостей никаких).

Запуск клиента так же незамысловат: iodine -P SECRET i.example.com.

После соединения мы получаем туннель с адресом, через который доступен удалённый сервер (в нашем случае — 10.99.99.1).

Можно пользоваться.

Можно.

Пользоваться.

А как?

Жизнь в конце туннеля

Если нас интересует что-то, кроме возможности сделать ssh на удалённый сервер, то придётся сделать кучу всего остального. Пост до этого момента был всего лишь художственным объяснением мана. Но практическое использование получившегося туннеля требует от нас решения проблем с сменой шлюза, сохранением «обычного» маршрута для DNS-серверов и преодоление проблем с фрагментированными пакетами.

Если грубо, то есть три метода использования получившегося туннеля:

  • Маршрутизация и NAT на сервере
  • socks-proxy
  • http-proxy (squid)

Разберём их все (и потом уже обсудим как с их настраивать). Основная проблема с маршрутизацией состоит в том, что MTU пакетов, проходящих через сеть, меньше 1500 байт. Много меньше. В зависимости от DNS-сервера не вашего провайдера, которого вы используете (в негативном смысле «использовать»), вы получаете MTU от 1344 (идеальный сценарий) до 740 байт. Другими словами, возникает фрагментация пакетов. А фрагментация пакетов это очень плохо. Это означает, что вероятность потерять пакет увеличивается в 2-3 раза, потому что если вы теряете хвостик пакета, вы его теряете целиком. Плюсом является простота и элегантность решения, а так же zero configuration для всего софта. Включил и работай. А ещё некоторые глупые CDN/IDC у некоторых сайтов блокируют фрагментированные пакеты.

С другой стороны, у нас есть возможность установить TCP-сессию с удалённым сервером (на котором iodined) через туннель, и в этом случае пакеты будут идти с размером MTU туннеля — никакого фрагментирования. Если в этот туннель мультиплексирвать другие tcp-соединения (на уровне потока, то есть sockets), то мы сможем общаться с окружающим миром пакетами такого размера, которые нам подходят. Однако, тут нас ждёт другая подстава. TCP ОЧЕНЬ не любит, когда канал плохой. В чём это выражается? Чем дольше работает TCP на канале с внезапными потерями и повторами, тем меньше он шлёт (считая, что пакеты теряются «из-за перегрузки»). В результате, скорость мультиплексированного канала постепенно падает, падает, падает… В случае если туннелирование происходит через SSH, то на это накладывается ещё и оверхед от шифрования. Где-то через минут десять при плохом DNS проходящие TCP-сегменты можно разглядывать поштучно в tcpdump/wireshark. Ещё одной проблемой являются внезапные задержки с поздними ответами, которые приводят к многочисленным dup ack (я не настолько хорошо знаю TCP, чтобы сказать, плохо это или совсем плохо). Однако, мне не удалось найти настроек ядра для изменения таймаута на перепосылку сегмента в TCP (кроме одиного define'а в сырцах, но до пересборки своего ядра под dns-tunnel я не успел дойти — интернет таки подключили, и интерес в развитии вопроса немного отпал). Заметим, это единственный способ, который можно использовать, если на удалённом сервере лучше никаких крупных изменений не вносить. В этом случае мы подключаемся к серверу командой ssh -CD 1080 10.99.99.1, прописываем socks-proxy в браузере на 127.0.0.1:1080 и имеем доступ в интернет. Из браузера. Только. Остальной софт настраивать соответствующе.

В принципе, для части программ может помочь утилита tsocks, принудительно заворачивающая трафик от приложения в socks-proxy. Используется она так: прописать в /etc/tsocks.conf правильный адрес прокси (127.0.0.1, port 1080, напоминаю), после чего запускать tsocks my_net_appication app-arguments.

Есть ещё один, более любопытный метод: мы можем поднять squid на сервере с iodined, сказать ему слушать на iodined интерфейсе (или на всех — но с acl'кой по адресам туннеля) и использовать нешифрованные соединения между клиентом и сервером. Таким образом мы получаем во-первых нефрагментированные пакеты, а во-вторых имеем множество TCP-сессий, которые сильно меньше подвержены деградации из-за кратковременных лагов DNS-сервера.

Минусов два: во-первых у нас нет сжатия (ssh умеет жать трафик, то есть все передаваемые заголовки в http оказываются отлично пожаты), во-вторых ssl проходит «как есть» или не проходит вообще.

Жизнь в начале туннеля: iodine-mn-helper

Ещё одной проблемой является конфигурирование шлюза по умолчанию в туннеле. Поскольку squid — http-only, то маршрутизацию для всего остального (почта по IMAP, Jabber, etc) нужно делать через NAT.

Проблемой является организация шлюза по умолчанию. Суть проблемы — если у нас dns-сервера находятся не в выданном нам сегменте (например, нам выдали 5.10.10.10/24, а DNS-сервера в 5.11.11.22 и 5.11.12.33), то если мы поменяем шлюз по умолчанию, то потеряем связь с DNS-серверами.

Определение текущих DNS-серверов так же довольно занудно. После некоторых мучений, я написал скрипт-хэлпер: github.com/amarao/iodine-nm-helper

Он весьма несовершенен, и дальнейшие улучшения приветствуются. Но он явно удобнее, чем скрипт iodine-client-start (прилагается к пакету с iodine), который не умеет разруливать проблемы c DNS.

Скрипт рассчитан на использование NetworkManger'а. К сожалению, повторый самоперезапуск скрипта после обновления аренды DHCP я не осилил, так что каждый раз, когда обновляется аренда, маршрутизация ломается и скрипт надо перезапускать.

Производительность и качество связи

Досточтимые ценители халявы, спешу разочаровать. DNS tunnels в реальных условиях (вне лаборатории) очень медленный и заменой нормального интернета не являются. Даже edge от tele2, уж насколько тормозной, и то быстрее. Снизу картинка из firebug'а при открытии github'а. Узрите и прослезитесь — это минуты загрузки, всё именно так и есть.

Скорость меняется крайне неравномерно. Я потратил некоторое время на изучение происходящего с tcp при работе через туннели — в силу специфичного поведения, наблюдается множество ретрансмитов и duplicate ack'ов, то есть tcp не может приспособиться к крайне неравномерной latency, когда любой пакет может «затупить» на единицы секунд, а может пройти за несколько десятков/сотен милисекунд.

В качестве минимального «что-то вместо ничего» он может использоваться. Например, в пике download'а (эм… гигантский файл в 1.6Мб размером) я наблюдал скорость до 8кб/с, но она быстро упала до нуля, и процесс пришлось несколько раз перезапускать. Если добрая cyta меня таки не подключит в ближайшие дни, то я зароюсь в дебри TCP чуть поглубже, чтобы найти эту заветную «retransmission timeout».

Проблема с dhcp. В PrimeTel у DHCP маленький срок аренды. При этом, при обновлении аренды происходит автоматическое обновление и маршрутов, то есть наш шлюз по умолчанию заменяется на паймтеловский. Почесав в затылке, я решил, что маршрут на сеть 0.0.0.0/0 (упс, поделил на ноль снова) будет круче. При этом остаётся проблема пропадающих маршрутов до DHCP, решения которой я пока не придумал (адреса выдают из разных сетей, в каждой такой сети свой шлюз, так что прибитые гвоздями маршруты не годятся, а прихватизировать адрес в чужой сети статикой я посчитал неэтичным и нарушающим правила пользования сетью).

Так что вопрос, вообще говоря, открытый.

Вопрос легальности

Наверное, самый сложный из. Формально, я получаю то, что не должен был получить, не заплатив за это много-много-много евро. С другой стороны, доступ к своим DNS-серверам оператор WiFi хотспота открыл добровольно и осознанно. Как именно я использую сервис — это уже мои проблемы, важно, что я не осуществляю никакого рода взломов (например, подбор пароля — уже очевидная уголовщина, несанкционированный доступ к вычислительной системе).

Полагаю, что этот вопрос находится в серой зоне (то есть точка зрения зависит от скилла адвоката в суде). В реальной же жизни — всем пофигу. Точнее, оператору может быть не пофигу, если его DNS сервера навернутся, но пока оно работает — никаких проблем. В свете тех фантастических скоростей, которые показывает iodine, всерьёз обсуждать «утрату» сотни килобит трафика даже не получается. В принципе, самое неприятное для оператора в данном случае это нагрузка на DNS и паразитный трафик в wifi-сети.

Настройки

(дальше скучно для всех, кроме тех, кто будет настраивать).

На сервере:

sysctl net.ipv4.ip_forward=1

iptables -t nat -A POSTROUTING -s 10.99.99.0/24 -j MASQUERADE

iodined -P SECRET 10.99.99.1/24 -c i.example.com

На клиенте:

vim /etc/iodine-nm-helper.conf (добавить настройки)

./iodine-nm-helper

(скрипт поправит маршрутизацию через туннель)

В firefox'е выставить http-прокси через ip сервера туннеля.

Конфиг сквида:

acl good src 10.99.99/24

http_access allow good localhost

http_access deny all

by t.me/it_ha