Перейти к содержимому

Парсер фишинговых доменов на Python: от инцидента с банковской рассылкой до рабочего скрипта

Парсер фишинговых доменов на Python: от инцидента с банковской рассылкой до рабочего скрипта
Время чтения: 12 мин.

За два месяца через наш SOC прошло четыре инцидента с фишинговыми рассылками от имени банков. Каждый раз аналитик тратил полтора часа, вручную вбивая подозрительные домены в браузерный WHOIS и проверяя их в VirusTotal. После третьего тикета я открыл Python и за вечер собрал скрипт, который делает ту же работу за десять минут. Четвёртый инцидент мы закрыли до обеда — abuse-запрос регистратору ушёл раньше, чем рассылка набрала объём.

Ниже — как этот парсер фишинговых доменов на Python устроен: от генерации тайпсквоттинг-вариаций до автоматической оценки риска, с кодом и пояснением каждого шага.

Зачем фишеру домены, похожие на банк: бизнес-логика фишинговой рассылки

Прежде чем писать код, разберёмся в мотивации атакующего — иначе неясно, что именно искать.

Фишинговая рассылка от имени банка — не хаотичная атака. В терминологии MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1566.002 — её уникальные идентификаторы) цепочка состоит из трёх шагов:

  1. Подготовка инфраструктуры (Resource Development). Атакующий регистрирует домен, визуально похожий на настоящий банковский — например, sber-0nline.ru вместо sberbank.ru. Это техника Domains (T1583.001). Домены в зонах .online, .site, .xyz стоят копейки и не жалко потерять через два дня.
  2. На домен вешается клон страницы авторизации с формой логина и пароля — техника Link Target (T1608.005). Часто страница защищена SSL-сертификатом от Let’s Encrypt, чтобы браузер не показывал предупреждение. Визуально — один в один настоящий интернет-банк.
  3. Жертвам уходит письмо со ссылкой на поддельный домен — Spearphishing Link (T1566.002, тактика Initial Access). Письмо маскируется под уведомление о блокировке карты, подозрительной операции или обновлении данных.

Монетизация — кража учётных данных: логин и пароль от интернет-банка, одноразовый SMS-код, данные карты. Домен живёт 24–72 часа, после чего регистратор или хостер его блокирует. За это окно атакующий собирает сотни пар «логин/пароль» и перепродаёт их или использует для вывода средств.

Именно это окно между регистрацией и блокировкой нужно сокращать. Ручная проверка не масштабируется: пока аналитик проверяет один домен, атакующий регистрирует десять. Python-скрипт для анализа доменов решает задачу иначе — генерирует все возможные вариации имени, проверяет их через WHOIS и ранжирует по риску.

Typosquatting detection на Python: генерируем вариации доменного имени

Typosquatting (тайпсквоттинг) — регистрация домена с опечаткой в имени известного бренда. Пользователь ошибается при наборе адреса или не замечает подмены в ссылке из письма. Родственный приём — brand impersonation (имитация бренда), когда к имени добавляют ключевые слова: sberbank-secure.ru, login-vtb.online. Третий вариант — TLD-squatting: занимают домен в другой зоне, например sberbank.online вместо sberbank.ru.

Основные приёмы подстановки, которые генератор должен покрыть:

  • Пропуск символа: sberbanksberbnk, sberank
  • Перестановка соседних букв: sberbanksberbnak
  • Замена на визуально похожий символ: o0, l1, a@
  • Ключевые слова: sberbank-online, secure-sberbank
  • Смена TLD (доменная зона верхнего уровня): .ru.online, .site, .xyz

Есть готовый инструмент [dnstwist](https://github.com/elceef/dnstwist), который делает всё это плюс IDN-омоглифы (когда кириллическая «а» подменяется латинской a). Но для встраивания в собственный пайплайн реагирования полезно понять механику и написать генератор самому. Нужен Python 3.8+, внешние библиотеки не нужны.

def generate_typos(brand, tlds=('.ru', '.com', '.online')):
    variants = set()
    for i in range(len(brand)):
        variants.add(brand[:i] + brand[i+1:])          # пропуск символа
    for i in range(len(brand) - 1):
        variants.add(brand[:i] + brand[i+1] + brand[i] + brand[i+2:])
    subs = {'a':'@', 'o':'0', 'i':'1', 'l':'1', 'e':'3'}
    for i, c in enumerate(brand):
        if c in subs: variants.add(brand[:i] + subs[c] + brand[i+1:])
    return [v + tld for v in variants if v != brand for tld in tlds]

Вызов generate_typos('sberbank') вернёт несколько десятков доменов: sberank.ru, sberb@nk.com, sberbank.online и так далее. Генератор создаёт только одиночные модификации — одна опечатка за раз, без комбинаций. Каждый результат — кандидат для WHOIS-проверки.

Дополнительно стоит добавить ключевые слова через дефис. Список вроде ['online', 'secure', 'login', 'pay'] и генерация f"{brand}-{kw}" для каждого. На практике я ещё дополнял укороченными формами: sber.online, sber-pay.ru. Для полноценного мониторинга фишинговых доменов стоит добавить омоглифы — но при разборе конкретного инцидента базовых подстановок хватает.

WHOIS-анализ подозрительных доменов на Python

Имея список вариаций, нужно проверить каждый домен через WHOIS (протокол запросов к базам данных регистраторов доменных имён). WHOIS отвечает на три вопроса: когда домен зарегистрирован, кем и через какого регистратора. В терминах MITRE ATT&CK — техника WHOIS (T1596.002, тактика Reconnaissance): мы используем публичные регистрационные данные для обнаружения вредоносной инфраструктуры.

По данным whoisjson.com и опыту разбора инцидентов, фишинговые домены имеют характерный набор признаков:

  • Возраст менее 30 дней. Большинство фишинговых доменов в активных кампаниях зарегистрированы за последний месяц. Легитимный банковский домен существует годами.
  • Скрытый регистрант. Атакующие используют privacy-proxy сервисы (Domains by Proxy, Withheld for Privacy) или указывают фиктивные данные. В WHOIS вместо имени владельца — REDACTED FOR PRIVACY или пустые поля.
  • Дешёвый TLD. Зоны .online, .site, .xyz, .top непропорционально часто встречаются в фишинге — атакующему не жалко «сжечь» такой домен через 48 часов.

Для автоматизации WHOIS-запросов из Python подходит библиотека [python-whois](https://pypi.org/project/python-whois/). Установка: pip install python-whois. Нужен Python 3.8+ и доступ в интернет.

def enrich_whois(domain):
    try:
        w = whois.whois(domain)
        cd = w.creation_date[0] if isinstance(w.creation_date, list) else w.creation_date
        if isinstance(cd, str): cd = None  # WHOIS иногда возвращает строку вместо datetime
        age = (datetime.now(timezone.utc) - cd.replace(tzinfo=timezone.utc)).days if cd else None
        reg = str(w.name or w.org or '').lower()
        priv = any(k in reg for k in ('privacy', 'redacted', 'proxy', 'withheld'))
        return {'domain': domain, 'age_days': age, 'is_private': priv}
    except Exception as e:
        # age_days: None может означать и свободный домен, и ошибку парсинга WHOIS
        print(f'WHOIS error for {domain}: {e}')
        return {'domain': domain, 'age_days': None, 'is_private': False}

Перед использованием: import whois и from datetime import datetime, timezone. Функция возвращает словарь с возрастом домена в днях и флагом приватности. Если домен не зарегистрирован, WHOIS бросит исключение — функция вернёт age_days: None. Это тоже полезно: домен свободен, атакующий его ещё не занял, но может занять завтра.

Ожидаемый вывод для подозрительного домена: {'domain': 'sberbank-0nline.ru', 'age_days': 3, 'is_private': True}. Домену три дня, регистрант скрыт — оба флага указывают на фишинг.

Нюанс по зоне .ru: WHOIS для неё возвращает значительно меньше полей, чем для .com. Имя регистранта чаще всего недоступно. Для .ru-доменов основной сигнал — возраст и имя регистратора, а не приватность контактов.

Мониторинг фишинговых доменов: Certificate Transparency и DNS как OSINT-инструменты для фишинга

WHOIS — не единственный источник. Два дополнительных сигнала заметно повышают точность анализа фишинговых доменов.

DNS-записи. Библиотека dnspython (pip install dnspython) позволяет проверить MX-записи (какой сервер принимает почту для домена) и A-записи (IP-адрес). Фишинговый домен часто не имеет MX — ему не нужно принимать почту, только отправлять. Проверка: dns.resolver.resolve(domain, 'MX') — если поднимется NXDOMAIN или NoAnswer, почтовых записей нет. Отсутствие DMARC-записи (политики email-аутентификации в DNS) и SPF (механизма ограничения отправителей) — ещё один характерный маркер. Фишинговые операторы не тратят время на настройку «одноразового» домена.

Certificate Transparency (CT). Когда атакующий получает SSL-сертификат для фишингового домена, факт выдачи фиксируется в публичных CT-логах — реестрах всех выпущенных сертификатов, доступных каждому. Сервис crt.sh даёт бесплатный API к этим логам. Запрос requests.get('https://crt.sh/?q=%25sberbank%25&output=json', timeout=10) вернёт JSON-массив со всеми сертификатами, в имени которых встречается sberbank. Фильтрация по дате выдачи (not_before) за последние 7 дней выявляет свежие домены, ещё не попавшие ни в один блоклист. По сути — техника DNS/Passive DNS (T1596.001, тактика Reconnaissance): публичные данные для обнаружения вредоносной инфраструктуры до начала активной фазы атаки.

В одном из инцидентов именно через crt.sh мы обнаружили домен, выпустивший сертификат два часа назад. WHOIS подтвердил — регистрация четыре часа назад. Abuse-запрос регистратору ушёл до начала рассылки.

Если домен уже резолвится в IP-адрес, дополнительно стоит проверить этот IP через AbuseIPDB — API для проверки репутации IP-адресов (бесплатный тариф: 1000 запросов в день). Показатель abuseConfidenceScore выше 75 — серьёзный повод для блокировки на уровне файрвола. Вместе WHOIS, DNS и CT-логи — базовый набор OSINT-инструментов для обнаружения фишинга, который покрывает весь жизненный цикл вредоносного домена: от регистрации до выдачи сертификата и активации.

Автоматизация incident response: скрипт оценки риска на Python

Отдельные сигналы — возраст, приватность, отсутствие MX — сами по себе слабые. Легитимный стартап тоже может иметь молодой домен с privacy-proxy. Комбинация нескольких сигналов превращает их в оценку, пригодную для решений. Подход простой: каждому сигналу — вес, итоговый балл определяет приоритет реагирования.

def score_risk(age_days, is_private, has_mx, similarity_pct):
    pts = 0
    if age_days is not None and age_days < 30:
        pts += 40                         # свежий домен — главный сигнал
    if is_private:
        pts += 15                         # скрытый регистрант
    if not has_mx:
        pts += 10                         # нет почтовой MX-записи
    pts += int(similarity_pct * 0.35)     # похожесть на бренд (0–100)
    return pts, ("CRITICAL" if pts >= 60 else "HIGH" if pts >= 40 else "LOW")

Параметр similarity_pct — процент сходства домена с оригинальным именем бренда. Для расчёта подходит библиотека thefuzz (бывшая fuzzywuzzy): fuzz.ratio('sberbank', 'sberbnk') возвращает число от 0 до 100. Перед сравнением нужно нормализовать строку — убрать TLD, дефисы и шумовые слова (secure, login, online). Без этого длинный домен sberbank-secure-login.ru получит низкий score из-за разбавления, хотя бренд в нём присутствует.

Как работает скоринг на практике:

Сигнал Баллы Почему
Домен моложе 30 дней 40 Самый весомый индикатор фишинга
Privacy-proxy 15 Скрытые контакты типичны для вредоносных доменов
Нет MX-записей 10 Домен не настроен для полноценной работы
Сходство с брендом До 35 Чем ближе к оригиналу — тем опаснее

Метка CRITICAL (60+ баллов) — домен на немедленный abuse-запрос регистратору и внесение в блоклист. HIGH (40–59) — ручная проверка аналитиком. LOW — в очередь на мониторинг.

Пример: домен sberbnk.online, возраст 5 дней, privacy-proxy, MX нет, сходство с sberbank — 85%. Расчёт: 40 (возраст) + 15 (приватность) + 10 (нет MX) + 29 (85 × 0.35) = 94 балла, метка CRITICAL.

Итоговый workflow для detection фишинговых рассылок от банка:

  1. Из фишингового письма извлекаешь домен ссылки
  2. generate_typos() создаёт список вариаций бренда
  3. Каждый вариант обогащается через enrich_whois()
  4. Проверяешь MX через dns.resolver.resolve()
  5. score_risk() расставляет приоритеты
  6. Домены с CRITICAL — abuse-запрос и блоклист
  7. Параллельно: проверка CT-логов через crt.sh для обнаружения доменов, которые генератор не покрыл

Для ускорения WHOIS-запросов — concurrent.futures.ThreadPoolExecutor с 5–10 потоками, но не больше: WHOIS-серверы режут частые обращения с одного IP.

Делай раз, делай два, делай три: поиск фейковых доменов банка на практике

Пошаговая инструкция для повторения. Нужны: Python 3.8+, pip, терминал (Linux/macOS или PowerShell на Windows).

Шаг 1. Окружение. Создай рабочую папку и виртуальное окружение: python3 -m venv phish-env && source phish-env/bin/activate. На Windows: phish-env\Scripts\activate. Если в начале строки терминала появилось (phish-env) — окружение активно.

Шаг 2. Зависимости. pip install python-whois "dnspython>=2.7.0" thefuzz requests. Проверка: python -c "import whois; print('OK')". Напечатало OK — всё на месте.

Шаг 3. Скрипт. Создай файл phishing_checker.py. Перенеси три функции из статьи: generate_typos, enrich_whois, score_risk. В main-блоке: прими имя бренда через sys.argv[1], сгенерируй вариации, обогати каждую через WHOIS (с time.sleep(2) между запросами, чтобы не получить блокировку), проверь MX (обернув dns.resolver.resolve(domain, 'MX') в try/except), рассчитай risk score и выведи результат.

Шаг 4. Запуск. python phishing_checker.py sberbank. Ожидаемый результат — таблица в терминале: domain | age_days | is_private | score | label. Домены с CRITICAL — первая очередь для abuse-запроса. Если все домены вернули age_days: None — они, вероятно, не зарегистрированы (но причиной может быть и ошибка парсинга WHOIS — проверь лог ошибок). Прямой угрозы нет, но имеет смысл поставить их на периодическую проверку: атакующий может зарегистрировать любой из них завтра.

Шаг 5 (расширение). Добавь проверку Certificate Transparency: requests.get(f'https://crt.sh/?q=%25{brand}%25&output=json'). Ответ — JSON-массив сертификатов. Фильтруй по полю not_before за последние 7 дней. Свежий сертификат для домена с именем бренда — повод немедленно прогнать этот домен через enrich_whois и score_risk.

Ограничение: WHOIS-серверы могут временно заблокировать IP при массовых запросах. Если скрипт начнёт получать пустые ответы или таймауты — увеличь задержку или сократи потоки в ThreadPoolExecutor.

По опыту четырёх инцидентов, самое трудное — не написать скрипт, а убедить коллег по SOC им пользоваться. Часть аналитиков продолжает проверять домены вручную — «скрипт для реагирования на инциденты это для разработчиков, а мы аналитики». Установка ложная. Python в SOC — не разработка продукта. Это автоматизация боли, которую чувствуешь каждую смену. Скрипт из этой статьи — несколько десятков строк. Ему не нужны unit-тесты и CI/CD. Он должен работать здесь и сейчас, пока на столе лежит тикет с фишинговой рассылкой от имени банка.

Второе наблюдение: WHOIS-данные деградируют с каждым годом. После GDPR в 2018-м большинство регистраторов стали скрывать контактные данные по умолчанию — даже без запроса владельца. Единственный надёжный сигнал — возраст домена — тоже теряет силу: отдельные группировки уже «выдерживают» домены месяцами перед кампанией. Пайплайн придётся пересматривать раз в полгода, добавляя сигналы из CT-логов и пассивного DNS. Статический инструмент в ИБ мёртв через шесть месяцев — и это та вещь, которую хочется узнать в начале пути, а не через год набивания шишек. Если переходишь из IT в ИБ и нужна структура вместо бесконечного собирания информации из случайных статей — на IB Basics базу закрывают за пару месяцев с лабами и менторами.

Эту тему и смежные навыки разбирают на практике в курсе «Основы программирования на Python» Codeby Academy.