Критическая уязвимость VLESS-клиентов: неаутентифицированный SOCKS5 на localhost

Дата публикации: 7 апреля 2026
Источник: runetfreedom — ntc.party, Habr
POC: github.com/runetfreedom/per-app-split-bypass-poc
Severity: Critical (массовая эксплуатация неизбежна)


TL;DR

Все популярные VLESS/xray/sing-box клиенты создают на localhost SOCKS5-прокси без аутентификации. Любое приложение-шпион на устройстве может подключиться к нему напрямую (минуя VpnService и per-app split tunneling), отправить запрос через прокси и узнать выходной IP VPN-сервера. Android Private Spaces (Knox, Shelter, Island) не защищают — loopback-интерфейс общий. Минцифры РФ разослало методичку по обнаружению VPN, включая сканирование характерных портов.


1. Предпосылки

Минцифры РФ официально потребовало от аккредитованных российских IT-компаний внедрения шпионских модулей в свои продукты для обнаружения и блокировки персональных VPN. Выпущена подробная методичка, описывающая:

  • Какие порты сканировать для обнаружения прокси
  • Какие VPN-признаки искать (флаги, интерфейсы, паттерны трафика)
  • Какую информацию собирать и передавать

Характерные Proxy-порты из методички Минцифры:

ТипПорты
SOCKS1080, 9000, 5555, 16000-16100
HTTP80, 443, 3128, 3127, 8000, 8080, 8081, 8888
Прозрачные Proxy80, 443, 4080, 7000/7044, 8082, 12345
Tor9050, 9051, 9150

Примечание: стандартные порты xray-клиентов (10808, 10809, 2080) в методичке пока не указаны, но скан всех портов localhost занимает секунды.

Яндекс уже использовал подобную технику ранее (сканирование localhost для обнаружения прокси).

Обновление (апрель 2026): Зафиксированы случаи активного подавления VPN-соединений сервисами Яндекса. При работе Яндекс.Музыки на мобильном интернете VPN-соединение через NekoBox (sing-box) принудительно сбрасывается. Переход на другой VPN-клиент (Karing) решает проблему, что указывает на fingerprinting конкретных VPN-протоколов/клиентов на уровне DPI.


2. Архитектура VPN-клиентов на Android

Нормальная цепочка прохождения трафика

Приложение
    ↓
VpnService (Android системный API)
    ↓
tun2socks (TUN-интерфейс → SOCKS)
    ↓
xray/sing-box SOCKS5 (localhost:10808)
    ↓
VPN-сервер (VLESS/Trojan/SS)
    ↓
Интернет (freedom)

VpnService — системный механизм Android, перехватывающий весь сетевой трафик и перенаправляющий его в TUN-интерфейс. Через него реализуется per-app split tunneling — выбор, какие приложения идут через VPN.

Ключевая деталь

SOCKS5-прокси xray/sing-box — это отдельный TCP-сервер, слушающий на 127.0.0.1:PORT. Он доступен любому процессу на устройстве через loopback, независимо от VpnService.


3. Суть уязвимости

Атака

┌─ Обычное приложение → VpnService → tun2socks ──┐
│                                                   ↓
│                                      xray SOCKS5 (:10808) → VPN-сервер → Интернет
│                                                   ↑
└─ Шпион (Яндекс/WB/Ozon/...) ─→ localhost:10808 ─┘
        (минуя VpnService, split tunneling не работает)

Шаг 1. Шпионский модуль сканирует порты localhost. Полный скан 65535 портов на loopback — секунды, нулевая нагрузка. Можно сканировать по триггеру (VPN-флаг системы).

Шаг 2. Найдя открытый SOCKS5 без аутентификации, шпион подключается и отправляет запрос на ifconfig.me (или аналог).

Шаг 3. Ответ содержит выходной IP VPN-сервера. IP передаётся в РКН → сервер блокируется.

Почему не помогает per-app split tunneling

Split tunneling работает через VpnService, который управляет TUN-интерфейсом. Но прямое подключение к 127.0.0.1:10808 не проходит через TUN — это внутренний loopback, он не маршрутизируется через VPN.

Почему не помогают приватные пространства (Knox/Shelter/Island)

Android Private Spaces изолируют:

  • ✅ Файловую систему
  • ✅ VpnService (отдельный VPN-профиль)
  • Loopback-интерфейс НЕ изолирован

Процесс из Knox/Shelter видит 127.0.0.1:10808 основного профиля. Это создаёт ложное чувство безопасности.

Почему смена порта не помогает

«Достаточно заменить порты на рандомные» — нет.

Скан портов на localhost занимает секунды. Шпиону не нужно знать порт заранее — он проверит все открытые порты и попробует SOCKS5 handshake на каждом.

Метод /proc/net/route: IP VPN-сервера раскрыт через таблицу маршрутизации

Помимо сканирования портов, существует ещё более простой метод обнаружения IP VPN-сервера:

Когда VPN активен, Android создаёт host route (маска /32 = 255.255.255.255) для IP VPN-сервера через реальный сетевой интерфейс (wlan0/rmnet0), минуя TUN-интерфейс VPN. Это необходимо, чтобы пакеты VPN-туннеля доходили до сервера через физическую сеть.

$ cat /proc/net/route
Iface   Destination  Gateway     Flags  Mask
tun0    00000000     00000000    ...    00000000    ← default через VPN
wlan0   B9A5C041     0201A8C0    ...    FFFFFFFF   ← host route к 65.197.165.185 через WiFi

Любое приложение может прочитать /proc/net/route без root.

Это раскрывает IP VPN-сервера для ВСЕХ типов VPN:

  • WireGuard / Amnezia VPN (UDP)
  • OpenVPN (TCP/UDP)
  • xray / VLESS / Trojan (TCP)
  • IPSec / IKEv2

Работает даже при раздельном туннелировании — маршрут к серверу всегда проходит через реальный интерфейс, иначе VPN-туннель не смог бы функционировать.

Вместе с /proc/net/udp и /proc/net/tcp (которые показывают ESTABLISHED-соединения всех приложений) шпион получает:

  • IP и порт VPN-сервера
  • UID приложения-владельца (→ имя пакета через PackageManager.getPackagesForUid())
  • Протокол (TCP/UDP → угадать тип VPN: UDP:51820 = WireGuard, TCP:443 = VLESS)

4. Happ: отдельная категория опасности

Клиент Happ (iOS) помимо уязвимого SOCKS5 включает xray gRPC API с HandlerService БЕЗ аутентификации.

Что такое xray API

xray-core поддерживает gRPC API с несколькими сервисами:

СервисНазначениеОпасность
StatsServiceСтатистика трафика, онлайн-пользователиНизкая
LoggerServiceУправление логированиемНизкая
HandlerServiceУправление inbound/outbound, добавление/удаление пользователей, дамп конфигурации🔴 КРИТИЧЕСКАЯ
RoutingServiceУправление правилами маршрутизацииСредняя
ReflectionServiceСписок доступных API-методовСредняя

Что позволяет HandlerService

  • Дампить полные конфиги outbound (включая ключи шифрования, IP сервера, SNI)
  • Добавлять/удалять inbound и outbound
  • Управлять пользователями протоколов (VMess, VLESS, Trojan, Shadowsocks)
  • Получать список всех inbound/outbound

Цепочка атаки на пользователей Happ

Шпион → скан портов → находит API (gRPC) → HandlerService
    → дамп outbound конфига → извлечение: ключ + IP сервера + SNI
    → [+ вторая уязвимость конфигурации] → расшифровка трафика

Позиция разработчиков Happ

Заявили, что API нужен для «сбора статистики подключения». Это ложь — для статистики достаточно LogService или StatsService. HandlerService предоставляет полный контроль над конфигурацией.

Реверс-инжиниринг показал: при смене конфига Happ перезапускает xray целиком, а не использует HandlerService для hot-swap. Легитимного применения HandlerService не найдено.

Рекомендация

  • Немедленно прекратить использование Happ
  • Заблокировать Happ на серверах подписок по UserAgent: Happ/*
  • Один пользователь с Happ компрометирует весь сервер (включая входной IP и SNI)

Подписочная система НЕ защищает

Распространённое заблуждение: «мой сервер на подписке, его не найдут». Это не так.

Через xray gRPC API (HandlerService) любое приложение на устройстве получает полную конфигурацию VPN-подключения:

ПолеПримерЧто это даёт шпиону
protocolNamesplithttpПротокол транспорта
address:portserver.amazonaws.com:443Адрес сервера → заблокировать
uuidf7fd6ed1-8cbb-…UUID → клонировать подключение
reality snicheckip.amazonaws.comSNI → идентификация
reality publicKeyqTdiDYiav3Rtv0kw…Публичный ключ

Подписка = сервер + UUID + ключи. Всё это хранится в xray-core и отдаётся через gRPC API без пароля. Любое приложение может прочитать вашу платную подписку целиком и передать данные для блокировки.

Доказательство: приложение per-app-split-bypass-poc демонстрирует полный дамп конфигурации через xray API на Android.


5. Техническая документация: аутентификация SOCKS5

xray-core: конфигурация SOCKS5 inbound

Без аутентификации (уязвимая конфигурация)

{
    "tag": "socks-in",
    "listen": "127.0.0.1",
    "port": 10808,
    "protocol": "socks",
    "settings": {
        "auth": "noauth",
        "udp": true
    }
}

С аутентификацией (безопасная конфигурация)

{
    "tag": "socks-in",
    "listen": "127.0.0.1",
    "port": 10808,
    "protocol": "socks",
    "settings": {
        "auth": "password",
        "accounts": [
            {
                "user": "randomUser_a8f3b2",
                "pass": "randomPass_7c4e91d0"
            }
        ],
        "udp": false
    }
}

Поля:

ПолеТипЗначение
auth"noauth" / "password"Режим аутентификации. По умолчанию "noauth"
accounts[{user, pass}]Массив учётных записей. Работает только при auth: "password"
udpbooleanВключение UDP. Важно: UDP в SOCKS5 фактически работает без авторизации
ipstringЛокальный IP для UDP-соединений
userLevelnumberУровень пользователя для политик

⚠️ SOCKS5 UDP проблема: протокол SOCKS5 аутентифицирует только TCP-соединение. UDP ASSOCIATE команда привязывает UDP-порт, но сами UDP-пакеты не проходят повторную аутентификацию. Для полной защиты UDP нужно отключать.

sing-box: конфигурация SOCKS/mixed inbound

Без аутентификации (уязвимая конфигурация)

{
    "type": "mixed",
    "tag": "mixed-in",
    "listen": "0.0.0.0",
    "listen_port": 2080
}

С аутентификацией (безопасная конфигурация)

{
    "type": "mixed",
    "tag": "mixed-in",
    "listen": "127.0.0.1",
    "listen_port": 2080,
    "users": [
        {
            "username": "randomUser_d4c1a7",
            "password": "randomPass_9e2f5b83"
        }
    ]
}

Поля:

ПолеТипЗначение
type"socks" / "mixed"mixed поддерживает SOCKS4/4a/5 и HTTP
users[{username, password}]Массив учётных записей. Без этого поля — без аутентификации
listenstringАдрес прослушивания
listen_portnumberПорт
set_system_proxybooleanАвтоматическая настройка системного прокси (Linux/Android/Windows/macOS)

Примечание: sing-box не создаёт mixed inbound автоматически — это делают клиенты и генераторы конфигов. Позиция разработчика sing-box: это «skill issue» клиентов.


6. Статус проверенных клиентов

Уведомление разработчикам разослано 10 марта 2026. Статус на 7 апреля 2026:

КлиентПлатформаЯдроSOCKS5 authСтатусПримечание
HappiOSxray❌ Нет🔴 Особо опасенAPI HandlerService без авторизации, дамп конфигов
v2RayTunAndroidxray❌ Нет🟡 УязвимРазработчик поблагодарил, обещал исправить
V2BOXiOS/Androidxray❌ Нет🟡 Уязвим
v2rayNGAndroidxray❌ Нет🟡 УязвимПорт 10808 по умолчанию
HiddifyAndroid/iOSsing-box❌ Нет🟡 Уязвим
ExclaveiOSxray❌ Нет🟡 Уязвим
Npv TunnelAndroidxray❌ Нет🟡 Уязвим
Neko BoxAndroidsing-box❌ Нет🟡 УязвимПорт 2080 по умолчанию
KaringAndroid/iOSsing-box❌ Нет🟡 УязвимПорты 3065-3067, можно добавить auth вручную
v2rayNWindowsxray❌ Нет🟡 УязвимМожно менять порт, но не добавить auth
HusiAndroidsing-box✅ Есть🟢 Можно настроитьLogin/pass на SOCKS
SFA (sing-box for Android)Androidsing-box✅ Есть🟢 Можно настроитьРучная настройка JSON
Xray (saeeddev94)Androidxray✅ Есть🟢 Можно настроитьF-Droid, ручной JSON конфиг

7. Статус XrayFluent (наш клиент)

Текущая конфигурация

Файл: xray_fluent/engines/xray/config_builder.py, строки 191-206

{
    "tag": "socks-in",
    "listen": PROXY_HOST,          # 127.0.0.1
    "port": int(socks_port),       # 10808
    "protocol": "socks",
    "settings": {
        "auth": "noauth",          # ❌ БЕЗ АУТЕНТИФИКАЦИИ
        "udp": True,               # ⚠️ UDP включён
    },
    "sniffing": {
        "enabled": True,
        "destOverride": ["http", "tls", "quic"],
        "routeOnly": True,
    },
}

Константы (xray_fluent/constants.py):

  • PROXY_HOST = "127.0.0.1" — только localhost ✅
  • DEFAULT_SOCKS_PORT = 10808 — стандартный порт ⚠️
  • DEFAULT_HTTP_PORT = 10809 — HTTP тоже без auth ⚠️
  • DEFAULT_XRAY_STATS_API_PORT = 19085 — API порт ⚠️

Шаблоны (data/templates/xray/default.json):

  • Слушают на 0.0.0.0хуже рантайма, доступны из сети ❌

Смягчающие факторы

XrayFluent — десктопный Windows-клиент, поэтому:

  1. Windows Firewall может блокировать доступ к localhost из других приложений (но не по умолчанию для loopback)
  2. xray routing rules с process name позволяют ограничить доступ
  3. Нет VpnService-подобного механизма — атака через loopback менее вероятна

Что нужно исправить

  1. Добавить "auth": "password" с рандомными credentials (генерировать при каждом запуске)
  2. Отключить UDP или реализовать UDP auth
  3. Исправить шаблоны: заменить 0.0.0.0 на 127.0.0.1
  4. Проверить, какие API-сервисы включены (не должно быть HandlerService)
  5. Добавить настройку в UI для включения/отключения SOCKS5 auth

8. Защитные меры

Для пользователей (клиентская сторона)

На Android:

  1. Использовать клиенты с поддержкой SOCKS5 auth (Husi, SFA, saeeddev94/xray)
  2. Если root: блокировать доступ к localhost через iptables для недоверенных приложений
  3. Немедленно удалить Happ и заблокировать на серверах подписок
  4. Держать российское ПО на отдельном устройстве
  5. Не полагаться на Knox/Shelter/Island — loopback не изолирован

На Windows:

  1. Настроить Windows Firewall для блокировки доступа к портам 10808/10809 для всех процессов кроме xray.exe и доверенных приложений
  2. Использовать рандомные порты (не 10808)
  3. В xray routing rules добавить process name для разрешённых приложений

На iOS:

  1. Ситуация сложная — большинство клиентов удалено из App Store и не получит обновлений
  2. Единственный вариант — клиенты с возможностью ручной настройки JSON (если доступны)

Для администраторов серверов

  1. Раздельные входной/выходной IP — если утёк выходной, входной продолжает работать
  2. CloudFlare WARP на выходе — как альтернатива второму IP: xray → WARP → интернет
  3. geoip:ru → block на сервере — заблокировать исходящий трафик на российские IP, чтобы шпионы не могли связать паттерн трафика с подключением
  4. Блокировка Happ по UserAgent: Happ/* на серверах подписок
  5. Мониторинг — отслеживать нетипичные запросы (ifconfig.me, ipinfo.io и аналоги) через прокси

Для разработчиков клиентов

  1. SOCKS5 auth по умолчанию — генерировать рандомные credentials при каждом запуске
  2. Отключить UDP в SOCKS5 или реализовать UDP-level auth
  3. Не включать xray API HandlerService — для статистики достаточно StatsService/LogService
  4. Не слушать на 0.0.0.0 — только 127.0.0.1
  5. Рандомизировать порт — не использовать стандартные 10808/2080/1080

Маршрутизация (клиентская сторона)

Рекомендуемая стратегия на клиенте:

geoip:ru      → direct   (российские IP напрямую)
geoip:private → direct   (локальные сети)
всё остальное → proxy    (через VPN)

На сервере:

geoip:ru      → block    (запретить выход на российские IP)
всё остальное → freedom  (разрешить)

В v2rayN для Windows эта стратегия уже реализована как пресет «Все, кроме РФ».


9. Технические детали: xray API (для понимания угрозы Happ)

Формат конфигурации API

{
    "api": {
        "tag": "api",
        "services": [
            "HandlerService",
            "LoggerService",
            "StatsService",
            "RoutingService"
        ]
    }
}

gRPC методы HandlerService

МетодОписаниеОпасность
AddInboundДобавить новый inboundВысокая
RemoveInboundУдалить inboundВысокая
AlterInboundИзменить inboundВысокая
AddOutboundДобавить outboundВысокая
RemoveOutboundУдалить outboundВысокая
AlterOutboundИзменить outboundВысокая
GetInboundUsersПолучить список пользователей inboundКритическая
GetInboundUsersCountКоличество пользователейНизкая

Как это эксплуатируется

  1. Шпион находит gRPC API на localhost (скан портов)
  2. Вызывает ReflectionService для получения списка методов
  3. Через HandlerService дампит outbound — получает ключ, IP, SNI
  4. В сочетании с уязвимостью конфигурации xray → расшифровка трафика

О второй уязвимости автор намеренно не раскрывает деталей.


10. Источники