Критическая уязвимость 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-порты из методички Минцифры:
| Тип | Порты |
|---|---|
| SOCKS | 1080, 9000, 5555, 16000-16100 |
| HTTP | 80, 443, 3128, 3127, 8000, 8080, 8081, 8888 |
| Прозрачные Proxy | 80, 443, 4080, 7000/7044, 8082, 12345 |
| Tor | 9050, 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-подключения:
| Поле | Пример | Что это даёт шпиону |
|---|---|---|
protocolName | splithttp | Протокол транспорта |
address:port | server.amazonaws.com:443 | Адрес сервера → заблокировать |
uuid | f7fd6ed1-8cbb-… | UUID → клонировать подключение |
reality sni | checkip.amazonaws.com | SNI → идентификация |
reality publicKey | qTdiDYiav3Rtv0kw… | Публичный ключ |
Подписка = сервер + 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" |
udp | boolean | Включение UDP. Важно: UDP в SOCKS5 фактически работает без авторизации |
ip | string | Локальный IP для UDP-соединений |
userLevel | number | Уровень пользователя для политик |
⚠️ 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}] | Массив учётных записей. Без этого поля — без аутентификации |
listen | string | Адрес прослушивания |
listen_port | number | Порт |
set_system_proxy | boolean | Автоматическая настройка системного прокси (Linux/Android/Windows/macOS) |
Примечание: sing-box не создаёт mixed inbound автоматически — это делают клиенты и генераторы конфигов. Позиция разработчика sing-box: это «skill issue» клиентов.
6. Статус проверенных клиентов
Уведомление разработчикам разослано 10 марта 2026. Статус на 7 апреля 2026:
| Клиент | Платформа | Ядро | SOCKS5 auth | Статус | Примечание |
|---|---|---|---|---|---|
| Happ | iOS | xray | ❌ Нет | 🔴 Особо опасен | API HandlerService без авторизации, дамп конфигов |
| v2RayTun | Android | xray | ❌ Нет | 🟡 Уязвим | Разработчик поблагодарил, обещал исправить |
| V2BOX | iOS/Android | xray | ❌ Нет | 🟡 Уязвим | |
| v2rayNG | Android | xray | ❌ Нет | 🟡 Уязвим | Порт 10808 по умолчанию |
| Hiddify | Android/iOS | sing-box | ❌ Нет | 🟡 Уязвим | |
| Exclave | iOS | xray | ❌ Нет | 🟡 Уязвим | |
| Npv Tunnel | Android | xray | ❌ Нет | 🟡 Уязвим | |
| Neko Box | Android | sing-box | ❌ Нет | 🟡 Уязвим | Порт 2080 по умолчанию |
| Karing | Android/iOS | sing-box | ❌ Нет | 🟡 Уязвим | Порты 3065-3067, можно добавить auth вручную |
| v2rayN | Windows | xray | ❌ Нет | 🟡 Уязвим | Можно менять порт, но не добавить auth |
| Husi | Android | sing-box | ✅ Есть | 🟢 Можно настроить | Login/pass на SOCKS |
| SFA (sing-box for Android) | Android | sing-box | ✅ Есть | 🟢 Можно настроить | Ручная настройка JSON |
| Xray (saeeddev94) | Android | xray | ✅ Есть | 🟢 Можно настроить | 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-клиент, поэтому:
- Windows Firewall может блокировать доступ к localhost из других приложений (но не по умолчанию для loopback)
- xray routing rules с
process nameпозволяют ограничить доступ - Нет VpnService-подобного механизма — атака через loopback менее вероятна
Что нужно исправить
- Добавить
"auth": "password"с рандомными credentials (генерировать при каждом запуске) - Отключить UDP или реализовать UDP auth
- Исправить шаблоны: заменить
0.0.0.0на127.0.0.1 - Проверить, какие API-сервисы включены (не должно быть HandlerService)
- Добавить настройку в UI для включения/отключения SOCKS5 auth
8. Защитные меры
Для пользователей (клиентская сторона)
На Android:
- Использовать клиенты с поддержкой SOCKS5 auth (Husi, SFA, saeeddev94/xray)
- Если root: блокировать доступ к localhost через iptables для недоверенных приложений
- Немедленно удалить Happ и заблокировать на серверах подписок
- Держать российское ПО на отдельном устройстве
- Не полагаться на Knox/Shelter/Island — loopback не изолирован
На Windows:
- Настроить Windows Firewall для блокировки доступа к портам 10808/10809 для всех процессов кроме xray.exe и доверенных приложений
- Использовать рандомные порты (не 10808)
- В xray routing rules добавить
process nameдля разрешённых приложений
На iOS:
- Ситуация сложная — большинство клиентов удалено из App Store и не получит обновлений
- Единственный вариант — клиенты с возможностью ручной настройки JSON (если доступны)
Для администраторов серверов
- Раздельные входной/выходной IP — если утёк выходной, входной продолжает работать
- CloudFlare WARP на выходе — как альтернатива второму IP:
xray → WARP → интернет - geoip:ru → block на сервере — заблокировать исходящий трафик на российские IP, чтобы шпионы не могли связать паттерн трафика с подключением
- Блокировка Happ по UserAgent:
Happ/*на серверах подписок - Мониторинг — отслеживать нетипичные запросы (ifconfig.me, ipinfo.io и аналоги) через прокси
Для разработчиков клиентов
- SOCKS5 auth по умолчанию — генерировать рандомные credentials при каждом запуске
- Отключить UDP в SOCKS5 или реализовать UDP-level auth
- Не включать xray API HandlerService — для статистики достаточно StatsService/LogService
- Не слушать на 0.0.0.0 — только 127.0.0.1
- Рандомизировать порт — не использовать стандартные 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 | Количество пользователей | Низкая |
Как это эксплуатируется
- Шпион находит gRPC API на localhost (скан портов)
- Вызывает ReflectionService для получения списка методов
- Через HandlerService дампит outbound — получает ключ, IP, SNI
- В сочетании с уязвимостью конфигурации xray → расшифровка трафика
О второй уязвимости автор намеренно не раскрывает деталей.
10. Источники
- Статья на ntc.party — оригинальная публикация runetfreedom
- Habr: Критическая уязвимость VLESS клиентов
- POC: per-app-split-bypass
- xray-core SOCKS inbound docs
- xray-core API docs
- sing-box SOCKS inbound docs
- sing-box Mixed inbound docs
- XTLS BBS: Malicious Activity of Closed-Source Xray Clients
- Meduza: Минцифры разослало методичку по VPN