Як хакери проводять розвідку перед атакою

Коли я запропонував колегам зробити recon власного сайту, щоб взяти скріни для цієї статті — вони розвели руками. Що там можна знайти, ми його тільки недавно зробили.

За 15 хвилин я знайшов річ, яку потрібно було фіксити в той же вечір — внутрішня docker-адреса нашого бекенду, яка просочилася у мінімізований клієнтський JS. І додатково — піддомен сервісу, який не світиться в публічних DNS-реєстрах, але katana все одно його витягла з нашого ж власного коду. Це не витік, але нагадування: будь-яке посилання у твоєму фронтенді — публічна інформація.

Жодна з цих знахідок не була результатом атаки. Жодної спроби «увійти». Усе — публічно. Усе — з відкритих джерел. Усе — за п’ятнадцять хвилин.

У цій статті я покажу всі команди. Щоб ви могли їх повторити на власному домені й побачити, що видно у вас.

Що таке recon і чим він відрізняється від атаки

Recon (розвідка) — це картографування цілі перед будь-якою атакою. Сам recon нічого не ламає. Він збирає інформацію: які піддомени існують, які з них живі, який стек, які API-endpoint’и, які залежності, які люди в команді.

Цей етап є невід’ємною частиною, оскільки спроба взлому без карти — це дії наосліп. Знаючи, що сайт на застарілому Nuxt, атакуючий йде у CVE-базу. Іноді recon показує щось, що навіть не потребує PoC, наприклад, API-ключ від якогось сервісу.

Розвідка ділиться на категорії:

🟢 Passive recon — запит іде до третьої сторони, не до тебе. crt.sh, subfinder, GitHub-пошук, Wayback Machine — усі вони опитують публічні бази, які про твою компанію вже знають. У твоїх логах не з’явиться жодного байта. Ти ніколи не дізнаєшся, що тебе таким чином дослідили.

🟡 Active recon — запит іде до твого сервера, але виглядає як один з тисячі звичайних відвідувачів. httpx-toolkit робить один GET /. katana завантажує JavaScript-файли так само, як це робить браузер кожного юзера. У логах це не відрізнити від легітимного трафіку.

Я провів цей recon на власну інфраструктуру. Покажу всі п’ять етапів і всі шість інструментів.

фінальна карта того, що знайшов

Інструмент № 1: crt.sh — публічний реєстр сертифікатів

Кожен раз, коли твій сайт отримує SSL-сертифікат, інформація про це потрапляє у публічні Certificate Transparency логи. Це обов’язково за стандартом — інакше браузер відмовиться довіряти сертифікату. Це гарна штука, бо ти можеш бачити, якщо хтось випустить серт на твій домен (атака). І це водночас гарна штука для атакуючого, бо він бачить всі сертифікати, які колись існували для твоєї компанії.

https://crt.sh/?q=%25.vapan.org

%25 — це URL-encoded %, wildcard. Всі сертифікати для будь-якого піддомена .vapan.org.

це насправді для github.com (там видніше), але принцип той же. Бачиш купу staging-, dev-, internal- піддоменів.

Те ж саме через термінал:

curl -s "https://crt.sh/?q=%25.vapan.org&output=json" 
| jq -r '.[].name_value' 
| sed 's/*.//g' 
| tr ',' '\n' 
| sort -u

вивід для vapan.org

Ключове: сертифікати з CT-логів видалити неможливо. Старі логи лежать там назавжди. Якщо у тебе колись був legacy-admin.yourcompany.com з сертифікатом — він і досі видимий, навіть якщо DNS-запис ти видалив п’ять років тому.

Інструмент № 2: subfinder + bbot

crt.sh — це одне джерело. Серйозний recon агрегує з 20+ джерел паралельно. Найшвидший спосіб — subfinder:

subfinder -d vapan.org -all -silent

Цей запит опитує: crt.sh, AlienVault OTX, VirusTotal, GitHub indexed, Shodan, Censys, Wayback, і ще близько 15 джерел. За 5 секунд.

Для контрасту — те ж саме на більших доменах:

vapan.org: 8 / shopify.com: 25 056 / cloudflare.com: 15 536

Малий сайт — 8 субдоменів, легко переглянути. Великий бренд — двадцять п’ять тисяч. Масштаб різний, принцип один: це публічна інформація, до якої кожен має доступ.

Більш просунутий інструмент — bbot. Це фреймворк з понад 50 модулями: subdomain enumeration, port scan через Shodan, JS-аналіз, email-харвестинг, GitHub-індексація. Замість шести окремих утиліт — одна команда:

bbot -t vapan.org -p subdomain-enum -y

— стартує

— фінальний звіт. Бачимо aggregate-табличку з ASN, IP-діапазонами, open ports з Shodan, MX-серверами. Усе без скана нашого сайту.

Інструмент № 3: httpx — який з знайдених піддоменів живий

З 8 піддоменів, які знайшов subfinder, реально працюючий — один. Як це перевірити? Інструмент httpx-toolkit:

cat all-subs.txt | httpx-toolkit -silent -title -tech-detect -status-code -follow-redirects -timeout 5

Що видає httpx крім «живий чи ні»:

  • Title сторінки
  • Tech stack (за заголовками, скриптами, реалізаціями)
  • Status code і ланцюжок редіректів
  • TLS-інформацію (якщо додати -tls-grab)

На моєму запиті для vapan.org httpx-toolkit повернув:

  • Live, статуси 301 → 200
  • Title: «Web Design & Development Services. Build Your Website Today»
  • Tech: Cloudflare, Cloudflare Browser Insights, HTTP/3, Node.js, Nuxt.js, Vue.js

Це не криптично, не приховано — це в HTTP-заголовках і у завантажених ресурсах. Будь-хто, хто відкриває твій сайт у браузері з відкритим DevTools, бачить це теж.

Що з цим робить атакуючий:
Він тепер знає, які CVE дивитися.
Спрямований пошук «Nuxt.js CVE 2025», «Vue.js prototype pollution», «Node 22 vulnerabilities».
Він точно знає, що шукати.

Інструмент № 4: katana + LinkFinder — JS-аудит, де я знайшов витоки

Сучасні веб-додатки — це single-page applications (Nuxt, Next, React, Vue, Angular). Це означає: коли ти відкриваєш сайт, твій браузер скачує великий пакет JavaScript-файлів. У цих файлах — вся логіка фронтенду, включно з:

  • адресами всіх API-endpoint’ів, на які ходить додаток
  • параметрами цих endpoint’ів
  • внутрішніми feature flags
  • умовами авторизації (UI-рівень)
  • іноді — конфіг для зовнішніх сервісів

katana — web crawler від ProjectDiscovery, який краулить сайт як браузер, виконує JavaScript, і тягне ВСІ URL — у тому числі JS-файли:

katana -u https://vapan.org -d 2 -jc -silent | grep -E ".js$" | sort -u > js-files.txt
wc -l js-files.txt

65 JS-файлів знайдено_

65 JavaScript-файлів. Це для невеликого корпоративного сайту. У великих SPA — сотні. Кожен — публічно завантажується кожним відвідувачем.

65 JavaScript-файлів. Це для невеликого корпоративного сайту. У великих SPA — сотні. Кожен — публічно завантажується кожним відвідувачем.

Далі — найцікавіше. Беремо ці файли і просто грепаємо по них патерни:

for js in $(cat js-files.txt); do
  curl -s --max-time 5 "$js" | grep -oE '"/[a-zA-Z0-9/_-]+"' | sort -u
done | sort -u | grep -E '^"/(api|v[0-9]|admin|auth|user|account)"'

І ось — карта мого власного API:

/api/articles
/api/articles/
/api/auth/login
/api/auth/me
/api/circle/stats
/api/form/submitFeedback
/api/news
/api/_nuxt_icon
/users/me

Відновлена з фронтенду, без авторизації, без жодного запиту на нестандартний URL. Усі ці endpoint’и працюють, я можу їх вручну спробувати. Не всі з них потребують повного login — і ось де атакуючий починає копати.

Що я знайшов на vapan.org за 15 хвилин

Два конкретні артефакти, які видно зі сторони — без жодного скана:

Знахідка № 1: внутрішня docker-адреса бекенду в клієнтському JS

У мінімізованих JS-файлах фронтенду я знайшов рядок виду backend:8080. Це внутрішнє ім’я Docker-контейнера, по якому фронтенд звертається до бекенду всередині нашої compose-мережі.

Чому це проблема:

  • Це підтверджує атакуючому, що сайт стоїть за Docker Compose або Kubernetes з іменованими сервісами. Стек одразу звужується.
  • Це підказує структуру внутрішньої мережі: є щось з ім’ям backend, воно слухає :8080. Якщо знайти SSRF чи незахищений preview-енд — є куди стукати.
  • Це помилка білда: внутрішнє ім’я не повинно потрапляти у клієнтський бандл. Має бути публічний proxy-шлях (/api), і збірка повинна обрізати все, що схоже на service-name:port.

Фікс: замінити прямі звернення на relative-URL (/api/...) і додати lint-правило, яке падає білд при знаходженні патерну [a-z]+:\d{2,5} у бандлі.

Знахідка № 2: піддомен, якого немає в публічних DNS-записах

katana витягла з JS посилання на ***.vapan.org — піддомен, який не з’являється у crt.sh, не індексується пошуком, не світиться у subfinder. На ньому стоїть Telegram WebApp, про який я знаю — це робочий сервіс.

Це не витік. Але це повчально:

  • Можна подумати, що про цей піддомен знають лише наша команда і Telegram. Виявилось, що достатньо одного <a href> у фронтенді — і будь-який katana/gau/waybackurls його витягне.
  • Принцип «security through obscurity» (захистились тим, що нічого нікому не казали) не працює. Якщо посилання є у твоєму HTML/JS — піддомен публічний, крапка.

Висновок: для приватних інтерфейсів (admin-панелі, internal-dashboard, staging) обов’язкова аутентифікація на рівні застосунку, а не сподівання, що ніхто не дізнається URL. Можна додатково ховати на рівні Cloudflare Access / IP allowlist — але це повинно бути додатковим шаром, а не єдиним.

Що з цим робити власнику сайту — 5-хвилинний чеклист

  1. Перевір crt.sh на свій домен. Відкрий https://crt.sh/?q=%25.вашдомен. Подивись, які піддомени там є. Якщо побачиш забуті dev/staging-домени — переконайся, що вони або взагалі видалені з DNS, або захищені (auth/IP allowlist) і не торкаються продакшен-даних.
  2. GitHub dork по своєму домену. На github.com у пошуку: "your-domain.com" filename:.env, "your-domain.com" filename:config.js. Якщо знаходить — швидко змінюй усі секрети, які могли там бути.
  3. Перевір власні JS у проді через katana — і потім grep по них на api/, localhost, :8080, _PRIVATE, secret, token. Зайве 95% знаходиться саме так.
  4. Налаштуй моніторинг нових сертифікатів. crt.sh має RSS-фід для домену — підпишись. Як тільки хтось випустить серт на твій бренд (фішинг або підман-у-середині), ти дізнаєшся першим.

Висновок

Recon — це не магія і не хакерство. Це рутинна процедура картографування, доступна будь-кому з ноутбуком. Шість утиліт, п’ятнадцять хвилин — і ти маєш повну карту: піддомени, стек, API, забуті сертифікати, витоки в JS.

Я зробив це на vapan.org і знайшов дві речі. Ти можеш зробити це на своєму домені просто зараз, тими ж командами з цієї статті, і побачити, що видно у тебе.

Якщо нічого — добре. Якщо щось — добре, що ти знайшов це першим, а не хтось інший.

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось21
До обраногоВ обраному14
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter