Black-box пентест: как одна панель управления раскрыла 30 поддоменов и Zabbix в открытом доступе

от автора

Предисловие: все названия компаний, имена разработчиков и реальные IP-адреса изменены. Технические детали и векторы атак — настоящие.

Недавно мне поступила задача: провести внешний black-box пентест клиентской панели управления. Входных данных — минимум: только URL. Ни IP-диапазонов, ни схемы сети, ни описания архитектуры.

Звучит как типичная история, но в процессе я наткнулся на такие грабли, что решил поделиться методологией. Статья будет полезна и начинающим пентестерам, и админам, которые хотят понять, как их инфраструктуру видят из интернета.

Этап 1: Разведка

Пассивный сбор

Начал я с банального — посмотрел, что отвечает на запросы.

curl -skI https://target-panel.example.ru

Сервер ответил:

HTTP/2 200 Server: nginx Via: 1.1 Caddy Set-Cookie: XSRF-TOKEN=... Set-Cookie: panel_session=... X-Frame-Options: SAMEORIGIN X-Content-Type-Options: nosniff

Уже интересно. Видно, что за веб-сервером стоит Caddy в качестве обратного прокси, а сам апстрим — nginx. Cookie вида XSRF-TOKEN и название panel_session намекают на Laravel.

Дальше — WAF-детекция:

wafw00f https://target-panel.example.ru

Результат: WAF не обнаружен. Это хороший знак для тестирования.

Активное сканирование портов

nmap -p- --min-rate=5000 -T4 -Pn -sS target-panel.example.ru

Результат меня удивил:

PORT    STATE    SERVICE 80/tcp  open     http 443/tcp open     https 65533   ports filtered

Из 65535 портов открыто только два. Это отличный показатель — межсетевой экран настроен грамотно. Большинство современных API-приложений так и работают: 80 443 редирект, 443 приложение.

Проверил и UDP — все порты фильтруются. Сеть под защитой.

SSL/TLS

Беглый анализ TLS показал, что с этим всё хорошо:

TLS 1.2: ECDHE-ECDSA-AES128-GCM-SHA256 (класс A) TLS 1.2: ECDHE-ECDSA-AES256-GCM-SHA384 (класс A) TLS 1.3: TLS_AKE_WITH_AES_128_GCM_SHA256 (класс A) TLS 1.3: TLS_AKE_WITH_CHACHA20_POLY1305_SHA256 (класс A)

Сертификат Let’s Encrypt на EC-ключе P-256, Heartbleed/POODLE/CCS Injection — всё чисто. Отлично, идём дальше.

Этап 2: Анализ веб-приложения

Детект технологий

Перенаправил браузер на страницу /login — встречает форма аутентификации с заголовком «Логин — Client Panel».

Посмотрел исходный код страницы. Сразу бросается в глаза:

<meta name="csrf-token" content="..."> <script src="https://cdn.tailwindcss.com"></script> <script defer src="/js/alpine.min.js"></script> <link rel="manifest" href="/manifest.json">

Плюс внизу страницы — PWA-регистрация:

navigator.serviceWorker.register("/sw.js");

И служебный скрипт alpine:init с функцией отправки AJAX-запросов:

async sendRequestWithHandleResponse({ url, method = 'GET', body = null, headers = {} }) {     const assignedHeaders = {         'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,         'Content-Type': 'application/json',         'Accept': 'application/json',         'X-Requested-With': 'XMLHttpRequest',         ...headers     };     ... }

Технологический стек на этом этапе:

Компонент

Технология

Бэкенд

Laravel (PHP)

API-защита

Laravel Sanctum

Фронтенд

Tailwind CSS + Alpine.js

Web-сервер

nginx + Caddy

PWA

Service Worker + Manifest 

Форма логина

Сама форма содержала четыре поля:

Поле

Тип

Плейсхолдер

Аккаунт

text

account-name

Почта

email

you@example.com

Пароль

password

••••••••

Запомнить меня

checkbox

 Мне показалось необычным, что требуется одновременно и account, и email. Возможно, account — это идентификатор клиента в биллинговой системе, а email — привязанная почта.

Этап 3: Обнаружение уязвимостей

3.1. Режим отладки на продакшене

Самая громкая находка. Я проверил, что будет, если отправить неподдерживаемый HTTP-метод на API-эндпоинт:

curl -sk -X POST -H "Accept: application/json" https://target-panel.example.ru/api/user

И получил полный stack trace:

{  "message": "The POST method is not supported for route api/user. Supported methods: GET, HEAD.",  "exception": "Symfony\\Component\\HttpKernel\\Exception\\MethodNotAllowedHttpException",  "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php",  "line": 131,  "trace": [    {      "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php",      "line": 116,      "function": "methodNotAllowed"    },    {      "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php",      "line": 56,      "function": "getAllowedMethods"    },    {      "file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",      "line": 802,      "function": "methodNotAllowed"    }  ] }

Тут же утекли:

  • полный путь на сервере: /var/www/html/;

  • версии фреймворка (пути vendor);

  • внутренняя структура роутинга;

  • три полных stack trace кадра.

Это классический APP_DEBUG=true. Казалось бы, сотни статей об этом написаны, а воз и ныне там. Ставлю CVSS 6.5 — при условии, что через debug не утекают APP_KEY и DB-credentials (в данном случае утекли только пути и архитектура). Если утекает APP_KEY, через deserialization возможна RCE, и тогда оценка поднимается до 7.5–9.0.

3.2. Cookie XSRF-TOKEN без HttpOnly

Посмотрел внимательнее на заголовки Set-Cookie:

Set-Cookie: XSRF-TOKEN=eyJpdiI6...; expires=...; Max-Age=7200; path=/; secure; samesite=lax Set-Cookie: panel_session=eyJpdiI6...; expires=...; Max-Age=7200; path=/; secure; httponly; samesite=lax

Видите разницу? Session cookie — httponly, XSRF-TOKEN — нет. На сайте к тому же отсутствует CSP — любой инлайн-скрипт или скрипт с CDN выполняется без ограничений. Комбинация не-HttpOnly токена и отсутствия CSP серьёзно упрощает атаку: найдите XSS — и токен ваш. Ставлю CVSS 6.1.

3.3. CORS нараспашку

Проверил CORS-заголовки:

curl -sk -H "Origin: https://evil.ru" -H "Access-Control-Request-Method: GET" -X OPTIONS https://target-panel.example.ru/sanctum/csrf-cookie

Ответ:

access-control-allow-origin: *

Wildcard CORS на эндпоинтах аутентификации. Да, с учётом того, что Access-Control-Allow-Credentials не выставлен, украсть сессию через браузер не выйдет. Но само наличие * позволяет любому сайту делать предварительные запросы и собирать информацию об API. CVSS 5.0.

3.4. Отсутствие HSTS

Несмотря на корректный редирект с 80-го порта (308 Permanent Redirect), заголовка Strict-Transport-Security не было. При первой загрузке через HTTP пользователь теоретически уязвим к SSL-stripping. CVSS 5.9.

3.5. Админка и API

Заметил по пути ещё пару эндпоинтов:

/admin          → 401 (Unauthorized) — панель администратора /api/login      → GET/HEAD разрешены, POST — 405 /api/user       → защищён middleware /sanctum/csrf-cookie → 204 (Sanctum CSRF)

Факт, что /admin отдаёт 401 в JSON, а не редирект на страницу логина, подсказывает, что это SPA-подобная архитектура: Angular/React/Vue на фронте, API на бэке.

Этап 4: Поддомены — золотая жила

Самое интересное началось, когда я перешёл к поддоменам. Использовал три источника:

  1. Certificate Transparency logs (crt.sh)

  2. Пассивное перечисление (Subfinder)

  3. DNS-запросы

Результат превзошёл ожидания — более 30 поддоменов:

target-panel.example.ru          ← клиентская панель (наша цель) dev1-panel.example.ru            ← ещё один dev-стенд dev2-panel.example.ru            ← и ещё один  api.example.ru                   ← API gpt-api.control-panel.example.ru ← GPT API (!)  passport.example.ru              ← аутентификация billing.example.ru               ← биллинг admin.billing.example.ru         ← админка биллинга  zabbix.control-panel.example.ru  ← ZABBIX nc.control-panel.example.ru      ← Nextcloud jitsi.example.ru                 ← Jitsi Meet exchange.control-panel.example.ru ← MS Exchange mail.example.ru                  ← почта  b24.example.ru                   ← Bitrix24? ai.example.ru                    ← сервис с AI

Что из этого доступно?

Я быстро проверил, какие из них отвечают:

zabbix.control-panel.example.ru    — 200 OK   «Zabbix docker: Zabbix»  ← открыт! jitsi.example.ru                   — 200 OK   «Jitsi Meet»             ← открыт! nc.control-panel.example.ru        — 200 OK   «Login – Nextcloud»     ← страница входа exchange.control-panel.example.ru  — 502       ← упал с ошибкой name-panel.example.ru          — 302       ← ещё один dev-стенд api.example.ru                     — timeout   ← не отвечает

Zabbix в открытом доступе без ограничения по IP — это, пожалуй, вторая по критичности находка после APP_DEBUG. Zabbix — лакомый кусок: через него можно узнать версии ОС, имена хостов, метрики производительности, а в старых версиях — получить RCE.

Jitsi Meet тоже открыт полностью — любой может создать комнату и общаться, используя инфраструктуру компании.

Этап 5: Промежуточные итоги

Матрица найденных уязвимостей

#

Уязвимость

CVSS

Приоритет

1

Laravel APP_DEBUG=true (stack trace наружу)

6.5

Высокий

2

Zabbix в открытом доступе

7.5

Высокий

3

CORS Wildcard Origin

5.0

Средний

4

Cookie XSRF-TOKEN без HttpOnly (сочетание с отсутствием CSP)

6.1

Средний

5

Отсутствие HSTS

5.9

Средний

6

Server-заголовок (nginx)

Инфо

7

Jitsi, Nextcloud наружу

5.0

Средний

Почему это произошло?

Судя по именам поддоменов (dev1, dev2, имена разработчиков), целевой сервер — среда разработки или стейджинг, не предназначенная для публичного доступа. Но она висела в открытом интернете, и Certificate Transparency logs исправно индексировали каждый поддомен.

Это классическая ситуация: DevOps-команда подняла стенд, забыла закрыть его VPN’ом или basic auth, и он оказался в общем доступе.

Этап 6: Что делать, если нашли такое у себя

Я составил список рекомендаций в порядке приоритета.

Критическое

  1. APP_DEBUG → false — проверьте .env на всех серверах. Laravel, Django, Rails — неважно. Режим отладки на продакшене недопустим.

  2. Закрыть Zabbix и админки — за VPN, IP-белый список, или хотя бы basic auth.

В ближайшую неделю

  1. CORS — * допустим только для публичных API. Для всего остального — явный список origins.

  2. HttpOnly на все cookie — особенно на токены.

  3. HSTS и CSP — без них современный сайт как дверь без замка.

В плановом порядке

  1. Аудит поддоменов — ct logs покажут всё, что вы забыли закрыть.

  2. Включить WAF, если провайдер предоставляет.

  3. Удалить неиспользуемые dev-стенды — каждый поддомен увеличивает поверхность атаки.

Заключение

Эта история про то, как одна маленькая ошибка (невыключенный debug) и один забытый сервис (Zabbix наружу) могут рассказать о компании больше, чем ей хотелось бы.

Каждый поддомен, прибитый гвоздями к публичному DNS, — это дверь. Не забывайте их закрывать.


Все данные анонимизированы. Технические детали атак соответствуют реальным.

Предоставленная информация о методах пентеста предназначена для повышения квалификации специалистов по безопасности и не может служить руководством к противоправным действиям. Распространение или использование этих данных во вредоносных целях противоречит законодательству РФ и преследуется по закону.

ссылка на оригинал статьи https://habr.com/ru/articles/1043478/