К контенту
habrforever
ЖИВА #wireguard 2026 · ~17 мин чтения

ч1: Обзор. Полгода после утечки через tun0. Кто из VPN-клиентов закрыл дыру, а кто закрыл issue — актуализация 2026

оригинал на Хабре habr.com/…/1088980
снимок Wayback не сохранялся
статус на Хабре (ноябрь 2026) доступна
права © автор, переиздание в архивных целях с указанием источника

ч1: Обзор. Полгода после утечки через tun0. Кто из VPN-клиентов закрыл дыру, а кто закрыл issue

Архивное переиздание с актуализацией. Оригинал опубликован на Habr (2026), автор — неизвестен. оригинал доступен на Habr по состоянию на ноябрь 2026. Ниже — редакция ноября 2026: что работает сейчас, что изменилось и как правильно повторить рецепт сегодня. Полный текст оригинала — в конце, без купюр.

TL;DR — версия 2026

WireGuard — по-прежнему лучший «нормальный» VPN (ядро Linux 6.x, RouterOS 7, все клиентские ОС), но в РФ его голый хендшейк режется DPI: используйте AmneziaWG (обфусцированный форк) на нестандартном UDP-порту.

С момента публикации оригинала WireGuard из «модной новинки» стал инфраструктурной нормой: он в mainline-ядре, во всех роутерах RouterOS 7, в NetworkManager и в «Настройках» iOS/macOS как нативный L3-протокол. Но именно эта популярность его и выдала: ТСПУ (DPI-боксы) стоят у всех крупных российских провайдеров и умеют резать VPN-хендшейки WireGuard, OpenVPN и «голый» Shadowsocks, а хендшейк WireGuard — статичная сигнатура на фиксированных портах (51820 и популярных «пере-портовках»). по данным РКН, за 2025 год в России заблокировано 258 VPN-сервисов. Поэтому в 2026 есть простое правило: WireGuard для своих сетей — да, WireGuard «напрямую в интернет из РФ» — только в обфусцированном виде.

Статус оригинала

AmneziaWG: WireGuard, который не видно

Форк AmneziaWG (AWG) добавляет в handshake мусорные пакеты, перемешивает порядок полей и прячет тип пакетов Initiation/Response — DPI не может собрать сигнатуру. Конфиги полностью совместимы с обычным wg (те же ключи, тот же Address/AllowedIPs), просто на обеих сторонах должен стоять amnezia-wg вместо vanilla wg. Варианты внедрения:

  • сервер: amnezia-wg из репозитория Amnezia или собрать из исходников; клиенты: приложение AmneziaVPN (все платформы) подхватит всё само;
  • панели: актуальные форки awg-easy (wg-easy с поддержкой AWG) — веб-интерфейс, QR-коды, как в оригинальной статье;
  • параметры обфускации (Jc, Jmin, Jmax, S1, S2, H1–H4) прописываются прямо в секции [Interface] конфига.

Сервер за 5 минут в 2026

Скрипт-установщик и Docker-образы из оригинала живы, но проверенные пути 2026:

# Debian 12/13, Ubuntu 24.04+
apt install amnezia-wg qrencode
awg genkey | tee privatekey      # ключ сервера
awg pubkey < privatekey          # публичный

wg0.conf/awg0.conf — тот же синтаксис, что в оригинале, плюс блок обфускации. Порт берите не 51820, а случайный высокий UDP (РКН исторически резал и 4500, и 9009, и 51820). Фаервол — как в оригинале: только ваш UDP-порт и SSH.

Site-to-Site и домашние сети: всё работает как раньше

Связка «две локалки через выделенный сервер» из оригинала не устарела ни на йоту: AllowedIPs с подсетями, PersistentKeepalive 25 на стороне за NAT. В 2026 добавились удобства: RouterOS 7 умеет WG нативно (/interface/wireguard), MikroTik hAP-стёки и GL.iNet Beryl AX прошиваются с AWG. Для(site-to-site) внутри РФ без выхода наружу обфускация вообще не нужна — режут только «наружные» хендшейки.

DNS: свою цепочку — обязательно

В 2026 нельзя оставлять системный DNS: провайдер резолвит «не тот» IP для заблокированных доменов. Минимум — DoT/DoH (dnscrypt-proxy как в оригинале, либо AdGuard Home), максимум — связка из оригинала Unbound+Pi-hole, где заблокированные домены уходят в отдельный upstream через туннель. Pi-hole с 2023 жив в v6 (FTL v6, web-интерфейс переписан), Unbound — в 1.2x.

Иллюстрации из оригинала

Две утечки

Что изменилось с 2026 года

  • wg в ядре Linux 6.x — kmod-WireGuard из DKMS больше не нужен
  • голый WG на 51820 режется DPI — обязателен AWG или нестандартный порт
  • wg-easy оригинальный не обновлялся — используйте живые форки (awg-easy и др.)
  • Cisco/meraki-стёки из «корпоративных» статей уступили zeroconf-альтернативам: Tailscale/Headscale/NetBird поверх WG
  • DNSCrypt-proxy из оригинала всё ещё жив, но чаще ставят AdGuard Home/Unbound
  • в тексте оригинала: классический OpenVPN легко детектится DPI (используется в корпсредах, но не для обхода блокировок)
  • в тексте оригинала: у «ванильного» WireGuard статичный хендшейк — DPI его режет; обфусцированный вариант — AmneziaWG

Чек-лист 2026

  • AmneziaWG на обеих сторонах (сервер + клиенты)
  • случайный высокий UDP-порт
  • PersistentKeepalive 25 за NAT
  • свой DNS (DoH/DoT) внутри туннеля
  • MTU 1280–1420 при CDN/облаках, иначе фрагментация

Вердикт

WireGuard в 2026 — это «электричество» частных сетей: скучно, стандартизовано, везде работает. Из оригинального гайда выбросите только одно — наивную веру, что его не видно. Ставьте AWG, и статья снова актуальна слово в слово.


ОРИГИНАЛ СТАТЬИ (без изменений)

ч1: Обзор. Полгода после утечки через tun0. Кто из VPN-клиентов закрыл дыру, а кто закрыл issue.

ч2: Разбор. Как мы закрывали утечку через tun0 в AmneziaVPN.

Надо отметить, что мы не имеем отношения к официальным разработчикам AmneziaVPN. Мы сторонние разработчики. Сделали PR-ы в их репозитории. И пока что их не приняли. И смотря на прошлые PR-ы в их репозитории, вероятность принятия наших PR-ов “как есть”, низка. В лучшем случае, мы надеемся что наша работа поможет разработчикам AmneziaVPN и других VPN клиентов.

TL;DR

На Android у VPN-клиентов две разные утечки адреса сервера. Пароль на локальный прокси закрывает первую и никак не касается второй.

Вторую, через привязку сокета к tun0, можно закрыть только в самом клиенте, проверяя, чьё соединение вышло из туннеля. Так сделали TeapodStream и OlConnect, и оба, судя по коду, пропускали как раз исключённые приложения, потому что про исключённое приложение Android отвечает «владелец неизвестен». TeapodStream исправил это в тот же день, когда мы ему написали, а OlConnect пока молчит.

В AmneziaVPN фикс лежит в PR, которые пока не приняты. sing-box даёт правило, но его нужно вписать в конфиг руками. v2rayNG закрыл отчёт об утечке как «not planned». От WireGuard, коммерческих VPN и Google мы не нашли ни слова.

Проверить свой клиент можно одной командой из Termux.

Одна команда

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

Возьмите Android-телефон с включённым VPN и добавьте Termux в исключения раздельного туннелирования, то есть скажите VPN-клиенту, что Termux должен ходить в интернет напрямую, мимо туннеля. Теперь выполните в Termux:

curl -m 10 --interface tun0 https://ifconfig.me/ip

Если в ответ пришёл адрес вашего VPN-сервера, утечка есть: приложение, которое по вашим же настройкам в туннель не входит, только что сходило через него и узнало адрес сервера. Ни root, ни своего кода для этого не понадобилось. Если curl отвалился по таймауту, попробуйте tun1 и tun2, потому что на нашем тестовом телефоне туннель назывался tun1. Таймаут на всех трёх — хороший знак.

Termux здесь играет роль любого приложения, которое вы исключили из VPN именно затем, чтобы оно VPN не видело, и это не теоретическая угроза. Открытый детектор RKNHardering, запущенный как приложение вне VPN, проходит тем же путём. В прогоне одного из ревьюеров наших PR он дошёл до сервера через tun0, и адрес сервера девять раз попал в его отчёт.

Мы делаем фикс этой утечки для AmneziaVPN, и до фикса AmneziaVPN вёл себя так же, как все: из шести попыток обхода до сервера доходили все шесть. Пока мы писали фикс, стало интересно, что сделали остальные. Весной об утечке писали много, а с тех пор прошло полгода, так что мы прошли по трекерам и релизам 28 клиентов, заглянули в китайские и англоязычные обсуждения, поискали, что говорит Google, и прочитали код тех, кто утечку закрыл. В этой статье обзор того, что получилось. Как устроен наш фикс и сколько он стоит, мы рассказываем во второй статье.

Две утечки, которые путают

Весной на Хабре обсуждали сразу две утечки, и в комментариях их постоянно смешивали, хотя причины и лекарства у них разные. Поэтому начнём с того, чтобы их развести.

Первая утечка связана с локальным прокси. Клиенты на Xray и sing-box обычно устроены так: VPN-интерфейс забирает трафик, tun2socks разбирает его на соединения и отдаёт ядру Xray через SOCKS-порт на 127.0.0.1, часто 10808. Но loopback общий для всех приложений телефона, и если у порта нет пароля, любое приложение может подключиться к нему и ходить в интернет через ваш сервер, исключено оно из VPN или нет. Об этом 7 апреля написал runetfreedom («Из-за критической уязвимости VLESS клиентов…») и назвал восемь уязвимых клиентов: Happ, v2RayTun, V2BOX, v2rayNG, Hiddify, Exclave, Npv Tunnel и NekoBox.

Вторая утечка устроена совсем иначе, и прокси тут ни при чём. У WireGuard и AmneziaWG никакого локального порта нет, а утечка есть: на AmneziaWG мы воспроизвели её сами. Чтобы понять, откуда она берётся, нужно знать, как работает раздельное туннелирование в Android. Оно держится на маршрутизации: когда VPN-клиент говорит системе «это приложение в туннель не пускать», Android прописывает правила по UID, и у приложений внутри VPN появляется маршрут в tun0, а у остальных его нет. Обычному сокету этого достаточно, потому что без маршрута пакет просто идёт мимо туннеля.

Но начиная с Linux 5.7 любое приложение может без всяких прав привязать сокет к интерфейсу вызовом setsockopt(SO_BINDTODEVICE, "tun0"). Для такого сокета ядро тоже ищет маршрут и тоже его не находит, однако пакет после этого не бросает, а решает, что адресат где-то «на линии» за названным интерфейсом, и отправляет пакет в tun0. Дальше всё происходит честно: VPN-клиент шифрует пакет, сервер выпускает его в интернет, а сайт видит адрес сервера.

Пароль на прокси против этого не помогает, потому что пакет идёт не через прокси, а через сам туннель. Ни одна настройка Android без root это тоже не запрещает. Закрыть утечку может только VPN-клиент, у которого есть файловый дескриптор туннеля: он должен смотреть, чей пакет из него вышел, и чужие не пропускать. Как это сделать и во что мы при этом упёрлись, мы разбираем во второй статье.

Две утечки

Две утечки

Есть и третья утечка, тихая. Xray-клиент AmneziaVPN на Android 13+ исключает адрес своего сервера из маршрутов VPN, а система публикует это исключение в свойствах VPN-сети, где его может прочитать любое приложение с разрешением ACCESS_NETWORK_STATE, не отправив ни одного пакета. Это нашёл один из ревьюеров наших PR. У AmneziaWG такого маршрута нет, мы проверили это на своём телефоне, а другие клиенты на этот счёт не смотрели.

И ещё одно различие, которое в комментариях часто теряется. «Приложение видит, что у вас VPN» и «приложение узнало адрес вашего сервера» — разные вещи. Первое почти не лечится, потому что tun0 виден, выдают задержки и есть другие признаки. Второе лечится, и оно важнее: адрес сервера могут заблокировать, и тогда сервер перестанет работать у всех, кто им пользуется. Эта статья только о втором.

Как это было

Самый ранний похожий отчёт, который мы нашли, датирован сентябрём 2025 года. В нём пользователь sing-box жаловался, что банковское приложение видит прокси, хотя в include_package его не было. Механизм там не назван, телефон был с root, и issue закрыли как «not planned». Всерьёз история началась весной 2026-го, и её удобно показать таблицей:

Дата

Что случилось

10 марта

runetfreedom сообщает разработчикам клиентов о локальном прокси (по его словам)

7 апреля

статья runetfreedom. В тот же день issue в sing-box, Hiddify, Karing и Throne

8 апреля

выходит детектор RKNHardering. Karing 1.2.16 добавляет пароль на локальный порт

9 апреля

Throne 1.1.2: пароль на локальный порт

10 апреля

sing-box добавляет правило package_name_regex (в стабильной версии — с 1.14.0, 31 августа)

12 апреля

TeapodStream: случайный порт и пароль (habr)

13 апреля

AmneziaVPN: случайный порт и пароль (#2456)

14 апреля

Anubis замораживает приложения на время работы VPN (habr)

17 апреля

v2rayNG 2.1.0: пароль и случайный порт, по желанию. В тот же день в v2rayNG приходит отчёт про tun0 — его закроют как «not planned»

22 апреля

«Android. Три буквы»: Shelter не спасает (habr)

4 мая

TeapodStream переходит на свой tun2socks с проверкой владельца соединения (habr)

10 августа

первый фикс для AmneziaWG от makekryl (amneziawg-go#174)

10 сентября

FlClash 0.8.97: пароль на локальный порт, по желанию

23 сентября

наша серия PR для AmneziaVPN

26 сентября

панель 3x-ui учится включать пароль в Happ через подписку (3x-ui#6628)

28 сентября

TeapodStream 1.6.6 закрывает обход в режиме «Все, кроме выбранных»

| Дата | Что случилось | | 10 марта | runetfreedom сообщает разработчикам клиентов о локальном прокси (по его словам) | | 7 апреля | статья runetfreedom. В тот же день issue в sing-box, Hiddify, Karing и Throne | | 8 апреля | выходит детектор RKNHardering. Karing 1.2.16 добавляет пароль на локальный порт | | 9 апреля | Throne 1.1.2: пароль на локальный порт | | 10 апреля | sing-box добавляет правило package_name_regex (в стабильной версии — с 1.14.0, 31 августа) | | 12 апреля | TeapodStream: случайный порт и пароль (habr) | | 13 апреля | AmneziaVPN: случайный порт и пароль (#2456) | | 14 апреля | Anubis замораживает приложения на время работы VPN (habr) | | 17 апреля | v2rayNG 2.1.0: пароль и случайный порт, по желанию. В тот же день в v2rayNG приходит отчёт про tun0 — его закроют как «not planned» | | 22 апреля | «Android. Три буквы»: Shelter не спасает (habr) | | 4 мая | TeapodStream переходит на свой tun2socks с проверкой владельца соединения (habr) | | 10 августа | первый фикс для AmneziaWG от makekryl (amneziawg-go#174) | | 10 сентября | FlClash 0.8.97: пароль на локальный порт, по желанию | | 23 сентября | наша серия PR для AmneziaVPN | | 26 сентября | панель 3x-ui учится включать пароль в Happ через подписку (3x-ui#6628) |

28 сентября TeapodStream 1.6.6 закрывает обход в режиме «Все, кроме выбранных»

Из таблицы видно, как история распалась на две волны. Первую утечку закрывали в основном в апреле, за считанные дни после статьи, потому что исправление там простое и понятное. Со второй всё медленнее: за полгода что-то сделали четыре проекта, и у двух из них защита поначалу не закрывала главный случай.

Первая утечка: закрыли почти все, но часто «по желанию»

С локальным прокси клиенты справились быстро, но по-разному, и разница прежде всего в том, включена ли защита сама:

Клиент

Что сделали

Версия, дата

Включено по умолчанию

AmneziaVPN

случайный порт и пароль при каждом подключении

#2456, 13.04

да

TeapodStream

случайный порт и пароль при каждом запуске

12.04

да

v2rayNG

пароль и случайный порт

2.1.0, 17.04

нет

Karing

пароль на mixed-порт

1.2.16, 08.04

не выяснили: код закрыт

Throne

пароль на входящие

1.1.2, 09.04

не выяснили

FlClash

случайные логин и пароль

0.8.97, 10.09

нет

Happ

переключатель пароля в клиенте; панель 3x-ui может включить его подпиской

3x-ui#6628, 26.09

зависит от панели

Hiddify

ничего: #2117 и #2120 закрыты без исправления

—

—

| Клиент | Что сделали | Версия, дата | Включено по умолчанию | | AmneziaVPN | случайный порт и пароль при каждом подключении | #2456, 13.04 | да | | TeapodStream | случайный порт и пароль при каждом запуске | 12.04 | да | | v2rayNG | пароль и случайный порт | 2.1.0, 17.04 | нет | | Karing | пароль на mixed-порт | 1.2.16, 08.04 | не выяснили: код закрыт | | Throne | пароль на входящие | 1.1.2, 09.04 | не выяснили | | FlClash | случайные логин и пароль | 0.8.97, 10.09 | нет | | Happ | переключатель пароля в клиенте; панель 3x-ui может включить его подпиской | 3x-ui#6628, 26.09 | зависит от панели |

Hiddify ничего: #2117 и #2120 закрыты без исправления — —

Пароль, который надо найти в настройках и включить самому, защищает в основном тех, кто читал Хабр в апреле, а все остальные так и сидят с настройкой по умолчанию. И даже включённый пароль закрывает не всё. Exclave с версии 0.17.35 предупреждает, что если на SOCKS5 включены и пароль, и UDP, то UDP паролем не защищён и обойти его легко.

Вторая утечка: проверка владельца и её ловушка

Со второй утечкой простого исправления нет. Клиенту приходится для каждого нового соединения из туннеля спрашивать у Android, чьё оно. Метод для этого есть с Android 10, ConnectivityManager.getConnectionOwnerUid, и отвечает он только активному VPN-сервису. Схема выглядит простой: получил UID владельца, сверил со списком исключений, чужое отклонил.

Так сделали двое. TeapodStream в мае переписал tun2socks и проверяет каждое соединение (статья Wendor, код в XrayVpnService.kt), а OlConnect проверяет пакеты в своём VPN-сервисе (OlcrtcVpnService.kt).

Ловушка в том, что именно отвечает Android. Естественно ожидать, что про пакет исключённого приложения метод вернёт его UID, и дальше останется найти этот UID в списке исключений. Но ConnectivityService в AOSP, найдя сокет, делает вот что:

if (uid == INVALID_UID) return uid;  // Not found.
if (hasNetworkStackPermission()) return uid;
final NetworkAgentInfo vpn = getVpnForUid(uid);
if (vpn == null || !isVpnServiceVpn(vpn)
        || vpn.networkCapabilities.getOwnerUid() != mDeps.getCallingUid()) {
    return INVALID_UID;
}

VPN-приложение узнаёт владельца, только если его VPN к этому владельцу применяется, а исключённое приложение — как раз тот случай, когда не применяется. Поэтому на его пакет метод отвечает INVALID_UID, тем же значением, что и на «сокет не найден». Наши логи это подтверждали: каждая попытка обхода от исключённого приложения приходила как «владелец неизвестен», и ни разу с настоящим UID. Для TCP есть и вторая причина, потому что сокет, привязанный к tun0, платформа не находит вовсе. Значит, правильная логика здесь одна: неизвестный владелец означает отказ.

Именно на этом, судя по коду, оба клиента и споткнулись. TeapodStream в режиме «Только выбранные», который стоит у него по умолчанию, всё делал верно, потому что неизвестного владельца нет в списке разрешённых и соединение отклоняется. А в режиме «Все, кроме выбранных» фильтр пропускал всё, чего нет в списке исключений, и -1 тоже. В комментарии в коде -1 был объяснён как пакеты от раздачи интернета, но ровно так же выглядит и обход от исключённого приложения. OlConnect пропускает INVALID_UID в любом режиме, первой же проверкой. Вдобавок он всегда пропускает запросы на порт 53, хотя DNS тоже выдаёт адрес сервера, пропускает всё, что не TCP и не UDP, и IPv6, а сама проверка, судя по коду, подключена только к их собственному протоколу OpenFlux.

Это вывод по коду: на телефоне мы эти клиенты не проверяли, потому что TeapodStream работает через Xray, а наш Xray-сервер из России не подключается, а сервера OpenFlux у нас нет. Обоим авторам мы написали не публично: в OlConnect 27 сентября, автору TeapodStream 28-го.

Автор TeapodStream ответил в тот же день и выпустил v1.6.6. Теперь в режиме «Все, кроме выбранных» соединение с неизвестным владельцем отклоняется, если в списке исключений что-то есть. Пропустить его снова можно только явной настройкой, которая разрешает раздачу интернета через туннель, и в коде честно сказано, что вместе с раздачей возвращается и обход. Автор OlConnect за четыре дня не ответил ни в issue, где мы просили закрытый канал для отчёта, ни новым релизом, так что если вы им пользуетесь, проверка из начала статьи займёт минуту.

Совсем другим путём пошёл sing-box. После issue #4009 защиты по умолчанию там не появилось, зато появился инструмент, правило package_name_regex (коммит 6746fcb, в стабильной версии с 1.14.0). С его помощью соединение, у которого sing-box не смог определить пакет, можно отклонить правилом, которое ставится первым:

"route": {
  "find_process": true,
  "rules": [
    { "package_name_regex": [".*"], "invert": true, "action": "reject" }
  ]
}

Логика та же, неизвестный владелец означает отказ. Мы это правило на устройстве не проверяли, и стоит учесть, что оно отклонит всё, для чего sing-box не нашёл пакет. Наш похожий фильтр в режиме «только выбранные» ломал Private DNS, так что после правила проверьте, что имена резолвятся.

В AmneziaVPN первый фикс для AmneziaWG предложил makekryl ещё в августе (amneziawg-go#174). Это только часть на Go, и она отбрасывает всё, что не может опознать. Наша серия от 23 сентября покрывает весь путь, от фильтра до переключателя в настройках (amneziawg-go#199, amneziawg-android#104, amnezia-client#3199), и неизвестный владелец у нас тоже означает отказ. На телефоне с включённым фильтром до сервера доходит 0 попыток обхода из 6, с выключенным все 6. Ни один из этих PR пока не принят.

Были и те, кто от исправления отказался. В v2rayNG#5499 пользователь воспроизвёл утечку той самой командой curl --interface tun0 из исключённого Termux, и issue закрыли как «not planned». В NekoBox висит #1112 с меткой «enhancement», где банк видит прокси, хотя исключён. Диагноза там нет, но симптом знакомый.

Большинство же просто молчит. Ни в трекерах, ни в блогах мы не нашли ничего об этой утечке у WireGuard for Android, Mullvad, Proton VPN, IVPN, Cloudflare WARP, Outline, Tailscale, NordVPN, Windscribe, Lantern, Psiphon, Orbot и OpenVPN for Android. «Не нашли» не значит «не сделали», но и публичного признания проблемы нет.

Молчит и Google. В AOSP мы не нашли ни бага, ни CVE, ни строчки в бюллетенях безопасности. В Android 17 появился системный экран, где пользователь выбирает приложения мимо VPN (ACTION_VPN_APP_EXCLUSION_SETTINGS), но, судя по описаниям, это новый экран выбора, а не новый механизм: об изменениях маршрутизации там ни слова. Позицию Google хорошо показывает соседний случай. В апреле исследователь показал другой обход VPN на Android 16, через системный сервис (Tiny UDP Cannon), и получил ответ «Won’t Fix (Infeasible)», а исправила его только GrapheneOS в сборке 2026050400.

Если свести всё вместе, на конец сентября картина такая:

Клиент

Как проверяет

Исключённое приложение через tun0

AmneziaVPN, наши PR

фильтр по владельцу, неизвестный — отказ

не проходит (проверено на телефоне), но PR не приняты

TeapodStream ≥ 1.6.6

фильтр по владельцу

не проходит в обоих режимах, пока не включена раздача (по коду; до 1.6.6 в режиме «Все, кроме» проходило)

OlConnect 1.6.5

фильтр по владельцу, только для OpenFlux

проходит (по коду; автор не ответил)

sing-box ≥ 1.14

правило вручную

не проходит, если правило вписано (не проверяли)

v2rayNG

нет, «not planned»

проходит (по отчёту в #5499)

остальные из обзора

ничего не нашли

—

| Клиент | Как проверяет | Исключённое приложение через tun0 | | AmneziaVPN, наши PR | фильтр по владельцу, неизвестный — отказ | не проходит (проверено на телефоне), но PR не приняты | | TeapodStream ≥ 1.6.6 | фильтр по владельцу | не проходит в обоих режимах, пока не включена раздача (по коду; до 1.6.6 в режиме «Все, кроме» проходило) | | OlConnect 1.6.5 | фильтр по владельцу, только для OpenFlux | проходит (по коду; автор не ответил) | | sing-box ≥ 1.14 | правило вручную | не проходит, если правило вписано (не проверяли) | | v2rayNG | нет, «not planned» | проходит (по отчёту в #5499) |

остальные из обзора ничего не нашли —

Что не помогает

В весенних обсуждениях предлагали и обходные пути, и почти все они от второй утечки не спасают. Чаще всего советовали рабочий профиль, Shelter или Island: кажется, что приложение в изолированном профиле не увидит туннель основного. Но профили Android разделяют данные и аккаунты, а сеть у них одна, и RKNHardering находит tun0 изнутри Shelter («Android. Три буквы»).

Не помогает и переименование интерфейса, потому что атакующему не нужно знать имя, он его перебирает: tun0, tun1 и так далее. Наша собственная проверка делает то же самое. На нашем телефоне с Android 16 Termux не смог получить список интерфейсов ни через if_nameindex, ни через /sys/class/net, всюду получая EACCES, а перебор имён при этом работает.

Пароль на SOCKS, как мы уже разобрали, закрывает только первую утечку. Anubis (habr) замораживает выбранные приложения, пока включён VPN, и это действительно работает, но цена понятна: банк недоступен, пока вы в туннеле.

С root вторую утечку можно закрыть правилом вроде iptables -A OUTPUT -o tun0 -m owner --uid-owner <UID> -j DROP и таким же для ip6tables. Мы это не проверяли, потому что root — отдельный разговор и большинству он недоступен.

Что делать сейчас

Прежде всего стоит проверить свой клиент командой из начала статьи, по очереди для tun0, tun1 и tun2. Она проверяет только TCP, поэтому, если хочется проверить и UDP, возьмите наш leak_probe.py. Он пробует TCP, три варианта UDP и два curl, работает в Termux без зависимостей и в конце пишет, сколько попыток из шести дошли до сервера. Запускать его нужно из приложения, исключённого из VPN.

Если у вас sing-box 1.14 или новее, впишите правило из раздела выше и прогоните проверку ещё раз.

Если нужен клиент, который закрывает утечку сам, выбор пока маленький. По коду это TeapodStream 1.6.6 и новее, если не включать в нём раздачу интернета. Для AmneziaVPN есть наша тестовая сборка strict.7. Это не официальный релиз: сборка подписана нашим ключом и ставится рядом с приложением из магазина, отдельным приложением «AmneziaVPN Strict». А если ваш клиент не защищает, напишите его разработчикам и приложите команду из начала статьи.

Тем, кто сам пишет VPN-клиент, пригодится вторая статья: там подробно о том, как устроен наш фильтр, во что мы упёрлись и что мы бы сделали с самого начала.

Чего мы не знаем

Мы не смотрели iOS, хотя в комментариях к весенним статьям об этом спрашивали: там другая модель per-app VPN, и тестового устройства у нас нет. Мы наверняка кого-то пропустили, и если ваш клиент закрывает утечку, а в обзоре его нет, напишите, мы добавим. И мы не нашли, заводил ли кто-нибудь баг в AOSP. Если заводили, поделитесь ссылкой.

Ссылки

Статьи

runetfreedom, «Из-за критической уязвимости VLESS клиентов…»: локальный прокси, 7 апреля

Wendor, «Критическая уязвимость VLESS клиентов? Подержите мое пиво…»: TeapodStream, пароль и случайный порт

sogonov, «Anubis: заморозка приложений по состоянию VPN»

Dertefter, «Android. Три буквы. Российские приложения»: методы детекта, Shelter

Wendor, «Когда пет-проект выходит из-под контроля: пишем свой tun2socks и закрываем дыры в Android VPN»: проверка владельца

Tiny UDP Cannon: другой обход VPN и ответ Google

Трекеры клиентов

sing-box: #4009, #3387, документация package_name_regex

v2rayNG: #5499

Hiddify: #2117, #2120

NekoBox: #1112

Karing: #1437

TeapodStream, OlConnect, RKNHardering

AmneziaVPN

issue #2457, с которого всё началось

amneziawg-go#174 (makekryl)

наша серия: amneziawg-go#199, amneziawg-android#104, amnezia-client#3199

заметки, утилиты проверки и тестовые сборки

по теме #wireguard