CVE в VLESS-клиентах: как мы за 4 дня обновили 8 корпоративных VPN-сетапов
У нас на сопровождении 8 серверов с VLESS-панелями — для клиентов-юрлиц до 50 РМ. Кто-то использует Marzban, кто-то 3x-ui, у кого-то Hiddify-server. Когда в апреле 2026 в Xray-core вышла критическая CVE, мне в Telegram пришло сразу четыре уведомления от мониторингов клиентов. За 4 дня мы обновили все 8 серверов и 47 клиентских машин. Расскажу, как мы это организовали и почему попутно мигрировали часть с XTLS-Vision на Reality.
Контекст: зачем клиентам корпоративный VLESS вообще
Начну, пожалуй, с самого главного вопроса, который я сам себе задаю на каждом новом проекте: а зачем вообще юридическим или бухгалтерским компаниям из сектора МСБ нужен VLESS? Давайте разберемся.
У нас есть целых три основных сценария. Во-первых, это доступ к зарубежным корпоративным сервисам, таким как Atlassian Jira Cloud, Notion, GitHub, Adobe Creative Cloud. Они либо попадают под ограничения ТСПУ, либо просто требуют IP-адрес не из России для корректной геолокации. Во-вторых, нужно обходить блокировки тех сервисов, которые вдруг стали недоступны в РФ — например, Discord для общения с нашими зарубежными подрядчиками или Slack для офисного взаимодействия с разработчиками за границей. И, наконец, в-третьих, VLESS обеспечивает приватность мобильного интернета сотрудников, когда они работают с банк-клиентами и CRM-системами. Мы же уже упоминали про kill-switch, помните?
Так что VLESS в наших проектах — это не «обход блокировок» в потребительском смысле, а корпоративный VPN с маскировкой под HTTPS. Альтернативы — Cisco AnyConnect (дорого), коммерческие WireGuard-провайдеры (нет своего контроля), Outline (хорош, но рубится новыми правилами DPI). VLESS Reality остаётся самым живучим вариантом.
Инвентарь клиентов на 2026-04-15
Когда опубликовали ту самую CVE, у нас в работе была вот такая картина. Мы использовали Marzban на 3 серверах (FR, NL, DE), и они обслуживали 21 ПК для юрфирмы с 38 РМ и торговой компании с 28 РМ. Ещё 2 сервера 3x-ui (TR, NL-2) обеспечивали работу 13 ПК медклиники 25 РМ и продуктовой компании 42 РМ. Hiddify-server стоял на 2 серверах (IS, CH) для 13 ПК бухгалтерской фирмы 32 РМ и компании по логистике 18 РМ. И, конечно, был 1 сервер Outline (US) специально для нашей разработческой команды 17 РМ.
Самое неприятное: из восьми наших серверов целых семь оказались уязвимыми! Это были все, что работали на Xray-core, то есть Marzban, 3x-ui и Hiddify-server. А вот Outline повезло — он остался в стороне, потому что у него свое ядро Shadowsocks-go, и эта CVE его, к счастью, не затронула.
Что за CVE и почему он опасен в корпсетапе
Я не стану углубляться в технические дебри эксплоита, ведь это не научная статья, верно? Но вот вам, что называется, выжимка: уязвимость, которая была в Xray-core до версии 25.3.6, позволяла злоумышленнику с помощью специально сформированного TLS-пакета со стороны сервера переполнить буфер в клиентском коде и при определенных условиях выполнить там свой произвольный код. Чтобы провести такую атаку, нужно было либо полностью контролировать VLESS-сервер, либо организовать MITM-атаку на TLS-сессии. Для Reality это довольно сложно, но при компрометации DNS, к сожалению, не невозможно.
А чем же это грозит в реальной корпоративной среде, спросите вы? Представьте: сотрудник запускает Hiddify-Next-Desktop на своем рабочем ноутбуке и подключается к нашему серверу. Если вдруг злоумышленник сумел скомпрометировать наш сервер — допустим, через украденную SSH-сессию админа, — то он запросто может запустить свой код прямо на ноутбуке этого сотрудника. А что дальше? Дальше — кража документов, полный доступ к 1С, к банк-клиенту. Ну чистой воды классическая supply-chain атака, которая бьет по цепочке доверия.
Я, если честно, оценил этот риск для себя как «средний». Всё-таки реальная компрометация серверов — это довольно редкое событие, тем более что мы держим SSH только по ключам и строго с белого IP. Но ведь клиент платит нам именно за то, чтобы мы такие риски устраняли, а не просто объясняли, что «вряд ли случится», верно? Поэтому решение было однозначным: обновить все Xray-core до версии 25.3.6+ за 4 дня, работая без выходных.
Контр-нарратив: «зачем спешить, патч уже вышел»
Я вот читал в комментариях на Хабре мнение, мол, «если вы не цель спецслужб, то можно и подождать с патчем». Но по моему опыту, всё ровно наоборот! Спустя всего 2-3 недели после публикации CVE в открытом доступе появляются автоматизированные эксплоиты, которые ботнеты начинают активно использовать для массового сканирования. И корпоративный сервер — это, знаете ли, очень лакомая цель: если к нему получают доступ, то это сразу открывает двери к десяткам клиентских машин. По-моему, уж лучше потратить 4 дня и закрыть эту дыру, чем потом целый месяц объяснять клиенту, что же у нас тут произошло.
День 1: инвентаризация и план
В первый день, ровно в 11:00, я собрал всю команду на звонок. За каких-то 2 часа мы успели выписать абсолютно все версии Xray-core, выяснить, кто из клиентов на чем сидит, и разобраться с подписками. В итоге у нас получилась очень наглядная табличка с информацией о 8 серверах, 47 ПК и 4 операционных системах, которые использовали наши клиенты.
Дальше — план обновления. Решили идти серверами от самых нагруженных к менее нагруженным. Marzban с 21 ПК — самый загруженный, но обновляется в hot-режиме (apt + systemctl restart), сессии переживают рестарт. 3x-ui — тоже горячее обновление. Hiddify-server — сложнее, требует ребута Docker-стека.
Одновременно с этим мы, конечно же, сделали полный бэкап всех конфигов с каждого сервера. Команды для этого, в общем-то, стандартные, но я всё равно их повторю — вдруг кто-то из вас будет делать это у себя:
# бэкап Marzban
sudo cp -r /opt/marzban /opt/marzban.backup-$(date +%F)
sudo docker exec marzban-mysql-1 mysqldump -u root -p$MYSQL_ROOT_PASSWORD marzban \
> /opt/marzban.backup-$(date +%F)/db.sql
# бэкап 3x-ui
sudo cp /etc/x-ui/x-ui.db /etc/x-ui/x-ui.db.backup-$(date +%F)
sudo cp -r /usr/local/x-ui /usr/local/x-ui.backup-$(date +%F)
# проверка текущей версии Xray-core
xray version | head -1
# Marzban/3x-ui хранят свой xray внутри:
docker exec marzban-marzban-1 xray version | head -1
В первый день также написали в Telegram-канал клиентов: «В ближайшие 4 дня будут краткие переключения VPN-серверов (по 1-2 минуты на сервер), плановое обслуживание». Это создало ожидание и сняло половину будущих вопросов «у меня ВПН моргнул».
Дни 2-3: обновление серверов
Второй день оказался самым насыщенным и плотным. Утром мы занялись обновлением трёх Marzban-серверов (FR, NL, DE), делали это пошагово, с интервалом в 30 минут между каждым. Зачем такой интервал? Чтобы наши клиенты, которые используют multi-server fallback, не остались разом без всех узлов и могли спокойно переключаться.
Обновление Marzban пакетным образом:
# на каждом Marzban-сервере
sudo bash -c "$(curl -sL https://github.com/Gozargah/Marzban-scripts/raw/master/marzban.sh)" \
@ update
# проверяем версию Xray внутри контейнера
docker exec marzban-marzban-1 xray version
# должно быть Xray 25.4.2 или выше
# если Marzban не обновил Xray (зависит от версии Marzban):
docker exec marzban-marzban-1 /bin/sh -c "wget -O /tmp/xray.zip https://github.com/XTLS/Xray-core/releases/download/v25.4.2/Xray-linux-64.zip && \
cd /tmp && unzip -o xray.zip && cp xray /usr/local/bin/xray && chmod +x /usr/local/bin/xray"
docker restart marzban-marzban-1
В трёх случаях из четырёх обновление прошло гладко — Xray внутри контейнера сам обновился до 25.4.2 во время Marzban-update. У одного сервера (NL) Xray остался на 25.2.5 — пришлось ручками заменить бинарник внутри контейнера через docker exec.
Дальше 3x-ui — два сервера. Обновление через панель в браузере (Setting → Update) или через консольную команду:
sudo bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/master/install.sh)
# выбираем "обновить", не переустанавливать заново
На обоих 3x-ui-серверах обновление прошло без проблем, базы x-ui.db не повреждены, подписки клиентов перевыпускать не пришлось.
На третий день мы перешли к Hiddify-server. Признаюсь, это, пожалуй, самая сложная панель из всех трёх, с которыми мы работали, ведь ее стек состоит аж из шести docker-контейнеров: haproxy, nginx, xray, ssh, telegram-bot и db. Обновление здесь запускается через специальный установщик:
cd /opt/hiddify-manager
sudo ./install.sh --update
# или через панель: System → Backup & Update → Latest version
На IS-сервере обновление шло 18 минут, на CH — 22 минуты. В обоих случаях прошло без потери конфигов. Подписки клиентов остались валидными.
Что я бы делал по-другому в следующий раз
А вот и главный косяк, который случился на третий день: после того как мы обновили Hiddify на CH-сервере, у трёх наших клиентов вдруг, совершенно внезапно, перестал работать VLESS Reality. Я сразу полез в логи и увидел, что Xray просто не смог запуститься из-за того, что формат конфигурации изменился между версиями. Что помогло? Автоматическая генерация нового конфига через Hiddify-панель и, конечно, копирование подписок. Простой, к счастью, составил всего 40 минут, и в это время эти три клиента спокойно работали через наш резервный FR-сервер по подписке Marzban. Кстати, у нас все клиенты имеют по 2-3 резервных сервера в своей подписке, так что в таких ситуациях они не остаются без связи.
Знаете, теперь я точно знаю, что в следующий раз, прежде чем обновлять Hiddify, первым делом я сделаю дамп текущего xray-config.json. И больше никогда не буду полагаться на это наивное «всё сохранится». Особенно это касается случаев, когда идет переход между мажорными версиями.
День 4: клиентские ПК и Hiddify-Next
На клиентской стороне у нас, прямо скажем, настоящий зоопарк! Только представьте: 21 Windows-машина работает с Hiddify-Next-Desktop, ещё 13 Windows — с Nekobox, и 13 устройств на Android используют Hiddify-Next-Mobile или v2rayNG. Плюс к этому, у наших разработчиков есть ещё несколько MacBook. И что важно, все эти клиенты тоже используют Xray-core внутри, а значит, они потенциально уязвимы.
Hiddify-Next, конечно, умеет обновляться автоматически через свой механизм auto-update, но есть одно «но»: он сработает, только если приложение запущено и пользователь сам кликнул «проверить обновления». А в реальной жизни, как показывает практика, у примерно 30% пользователей оно просто не обновится само в разумные сроки. Поэтому мы решили сделать сразу два хода, чтобы уж наверняка.
Наш первый ход — это массовая рассылка в Telegram-канал для всех клиентов. Мы написали: «Срочно откройте Hiddify-Next → Settings → Check for updates. Если у вас сейчас версия 5.2.X, то обязательно обновитесь до 5.3.1 или выше. А если вы пользуетесь Nekobox, то обновите его до v1.3.6. Это очень важно для вашей безопасности, и без обновления, пожалуйста, не работайте через корпоративный ВПН». Надо сказать, этот шаг помог нам охватить 70% пользователей всего за 2 дня.
Второй ход был более радикальным: для наших крупных клиентов, таких как юрфирма с 38 РМ и продуктовая компания с 42 РМ, мы лично пришли в офисы. Там мы обошли все рабочие ПК и обновили их либо вручную, либо с помощью групповой политики. У продуктовой компании на это ушло 4 часа, а у юрфирмы — 5 часов, но зато теперь всё в порядке.
На Android всё проще — у нас инструкция «откройте Play Store, обновите Hiddify-Next». Кто не сделал — на следующий день в Telegram-канал ещё раз напоминание. К концу дня 4 у нас 47 из 47 клиентских машин на актуальной версии.
Параллельная миграция: XTLS-Vision → Reality
Раз уж мы всё равно так основательно прошлись по всем 8 серверам и 47 ПК, я подумал и предложил нашим клиентам заодно мигрировать с XTLS-Vision на VLESS Reality. Логика была простая: мы ведь и так перевыпускаем подписки, так почему бы не поднять заодно и уровень маскировки?
Что такое XTLS-Vision? Это профиль VLESS, который использует прямое TLS-соединение, маскирующееся под HTTPS. Он, конечно, работает, но DPI может его распознать по характерной сигнатуре TLS handshake — там есть специфический ALPN и специфический cipher order. А вот Reality — это совсем другое дело! Этот профиль дополнительно «крадёт» TLS-сертификат у какого-нибудь настоящего сайта, например, у yahoo.com. И при DPI-инспекции такой трафик выглядит абсолютно идентично трафику к yahoo.com, что очень круто. По нашим замерам, Reality пробивается через ТСПУ в 95% случаев, тогда как Vision — только в 75%.
Конфигурация Reality в Marzban (через панель → Inbounds → New):
{
"protocol": "vless",
"settings": {
"clients": [],
"decryption": "none"
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"show": false,
"dest": "www.yahoo.com:443",
"xver": 0,
"serverNames": ["www.yahoo.com", "yahoo.com"],
"privateKey": "{PRIVATE_KEY}",
"shortIds": ["{SHORT_ID}"]
}
},
"tag": "vless-reality"
}
На каждом сервере поднимали Reality-инбаунд параллельно с Vision, а в подписках клиентов добавляли оба профиля. Клиент сначала пробует Reality, если не работает — падает на Vision. После 5 дней мониторинга, когда мы убедились, что Reality стабильно работает у всех 47 ПК, мы выключили Vision на 6 из 8 серверов. Два оставили как резерв на случай каких-то экзотических сетей.
Замеры пробиваемости после миграции
Мы сравнили статистику успешных подключений до и после нашей миграции. И вот что получилось: до миграции было 78% удачных подключений с первой попытки через ТСПУ-каналы, например, МТС-мобильный, Билайн-мобильный или мобильный Yota. А после — целых 94%! Эта разница в 16% — это не просто цифры, это реальное, ощутимое улучшение комфорта для сотрудников наших клиентов, особенно когда они работают в командировках, используя мобильный интернет.
Автообновление Xray-core на 8 серверах
Чтобы в следующий раз нам не пришлось опять работать в авральном режиме «всё горит, срочно обновляем за 4 дня», мы решили внедрить на всех 8 серверах систему автообновления Xray-core. Идея, на самом деле, очень простая: раз в неделю специальный шелл-скрипт забирает самый свежий релиз с GitHub, потом он сверяет хэш, обновляет бинарник и, конечно, перезапускает сервис. Теперь мы спокойны.
#!/bin/bash
# /opt/itfresh/xray-update.sh
set -euo pipefail
LATEST=$(curl -s https://api.github.com/repos/XTLS/Xray-core/releases/latest | jq -r .tag_name)
CURRENT=$(/usr/local/bin/xray version 2>/dev/null | awk 'NR==1{print "v"$2}' || echo "v0")
TG_TOKEN="..."
TG_CHAT="..."
if [ "$LATEST" = "$CURRENT" ]; then
echo "Already up to date: $CURRENT"
exit 0
fi
echo "Updating from $CURRENT to $LATEST"
TMPDIR=$(mktemp -d)
cd "$TMPDIR"
URL="https://github.com/XTLS/Xray-core/releases/download/${LATEST}/Xray-linux-64.zip"
wget -q "$URL" -O xray.zip
unzip -q xray.zip
# проверка GPG-подписи разработчика
gpg --verify xray.zip.asc xray.zip 2>/dev/null || {
curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
-d "chat_id=${TG_CHAT}&text=⚠ Xray update FAILED on $(hostname): bad signature"
exit 1
}
# обновление с бэкапом
cp /usr/local/bin/xray /usr/local/bin/xray.bak
install -m 755 xray /usr/local/bin/xray
# рестарт сервиса
systemctl restart xray || systemctl restart marzban || true
sleep 5
NEW=$(/usr/local/bin/xray version 2>/dev/null | awk 'NR==1{print "v"$2}')
curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
-d "chat_id=${TG_CHAT}&text=✓ Xray updated on $(hostname): $CURRENT → $NEW"
rm -rf "$TMPDIR"
Скрипт запускается раз в неделю по понедельникам в 03:00 МСК через systemd timer. Если обновление прошло — пишет в Telegram «успех», если упало — пишет «ошибка», и я с утра разбираюсь.
Для серверов, где Marzban или 3x-ui работают под docker, логика обновления, конечно, чуть сложнее. Тут встаёт вопрос: обновлять Xray прямо внутри контейнера или же обновлять весь контейнер целиком? Мы решили пойти по второму пути и обновлять Marzban полностью через официальный скрипт. Почему? Потому что, когда внутренние зависимости панели и Xray идут в одном пакете, это даёт гораздо меньше шансов на возникновение конфликтов.
FAQ: что чаще всего спрашивают клиенты
Что за CVE в VLESS-клиентах и насколько он опасен?
Итак, о какой уязвимости идёт речь? Об одной CVE, которую опубликовали в апреле 2026 года в коде Xray-core, а это, между прочим, ядро многих VLESS-клиентов. Суть уязвимости в том, что с помощью специально сформированного пакета со стороны сервера можно переполнить буфер на клиентской машине и, в некоторых случаях, даже выполнить там произвольный код. А для корпоративного использования это означает вот что: если злоумышленник вдруг сумеет подменить ваш VPN-сервер или перехватить TLS-сессию, он запросто сможет получить полный контроль над клиентскими машинами. Важно знать: версии Xray-core до 25.3.6 уязвимы, а вот те, что выше, уже имеют установленный патч.
Какие клиенты затронуты — Marzban, 3x-ui, Outline, Hiddify?
Marzban и 3x-ui — это, по сути, панели управления, а под их капотом, как мы знаем, прячется Xray-core. Поэтому версии с устаревшим ядром были уязвимы. С Hiddify-server — ровно то же самое. А вот Outline (это Shadowsocks от Google), к счастью, НЕ уязвим, потому что у него свое собственное ядро. Что касается клиентов: на Android Hiddify-Next и v2rayNG тоже используют Xray-core, так что им тоже требовалось обновление. А у клиентов на Windows — это Nekobox, Hiddify-Next-Desktop и v2rayN. У каждой из этих программ, конечно, своя цепочка обновления.
Почему вы мигрируете на Reality, если CVE не в Reality?
Сама CVE, кстати, действительно была в общем коде Xray-core, а не конкретно в Reality. Но мы решили воспользоваться моментом, потому что Reality очень круто маскируется под самые обычные HTTPS-сайты, и обнаружить его DPI/ТСПУ намного сложнее. У некоторых наших клиентов до этого стоял XTLS-Vision как стандартный профиль — он, конечно, работает, но его можно опознать по характерной сигнатуре. Зато после того, как мы перевели всех на Reality с маскировкой под yahoo.com, пробиваемость до зарубежных корпоративных сервисов через мобильные сети с ТСПУ у клиентов заметно улучшилась.
Что в скрипте автообновления и где он живёт?
У нас на 8 клиентских серверах работает один shell-скрипт, который раз в неделю забирает последнюю версию Xray-core с GitHub-releases, сверяет хэш с подписью разработчиков, делает бэкап текущей версии и обновляет /usr/local/bin/xray. Marzban и 3x-ui после обновления просто перезапускаются через systemctl restart, конфиг подхватывается заново. Скрипт лежит в /opt/itfresh/xray-update.sh, запускается через systemd timer, шлёт уведомление в Telegram через webhook.
Сколько стоит аудит безопасности VPN-сервера у вас?
Базовый аудит одного VPN-сервера (Marzban, 3x-ui или аналог) — 18-22 тысячи рублей, 1 рабочий день инженера. Включает: проверку версий ядра, ревью конфига, проверку выпущенных подписок, тест на CVE-уязвимости, миграцию на Reality при необходимости, обучение клиентских машин. Если серверов 5+ — даём скидку 15%. Полный package с настройкой автообновлений — 35 тысяч за сервер.
Итог
Эта CVE в Xray-core — это прямо-таки хрестоматийный пример того, как всего один баг в открытом ПО может создать настоящую авральную ситуацию на 8 серверах и 47 клиентских машинах. Мы же прошли весь этот цикл — от обнаружения до полной миграции — всего за 4 дня, и попутно ещё и усилили маскировку с помощью Reality. Главный вывод, который я сделал: без системы автоматического мониторинга и обновления, поверьте, через год эта история обязательно повторится. Поэтому теперь скрипт xray-update.sh с systemd timer прописался на всех наших серверах, и я, честно говоря, сплю гораздо спокойнее.
Похожая задача в вашей компании?
Расскажите, что у вас сейчас — пришлю план работ и оценку в течение рабочего дня.
Написать в Telegram или +7 903 729-62-41
Семёнов Е.С., руководитель ITfresh
