Как антифрод-системы маркетплейсов палят мультиаккаунты по корреляции часовых поясов и языка браузера
Содержание
- Что вообще видят антифрод-системы
- Как работает связка TZ + IP
- Что именно собирает JS
- Расхождение языка и таймзоны — главный маркер
- Как это выглядит в логах антифрода
- Почему подмена TZ через DevTools не спасает
- Связка с поведенческими метриками
- Кейс: сеть аккаунтов на одном прокси-пуле
- Кейс: несовпадение языка и валюты
- Как выглядит корректная маскировка
- Что делать с прокси
- Итоговая логика антифрода
Что вообще видят антифрод-системы
Мультиаккаунт палится не по одному признаку. Никогда. Антифрод маркетплейса — это граф связей, где каждая сессия оставляет десятки узлов: IP, ASN, часовой пояс, язык, шрифты, WebGL, canvas-хеш, поведение мыши, время между действиями. Задача системы — не найти "плохого", а построить кластеры, где аккаунты подозрительно похожи.
Часовой пояс и язык браузера — два самых дешёвых сигнала. Их собирают первыми. Они не требуют ни капчи, ни отпечатка железа, ни анализа поведения. Просто JS-скрипт при загрузке страницы читает `Intl.DateTimeFormat().resolvedOptions().timeZone`, `navigator.language`, `navigator.languages` и отправляет на сервер. Всё. Дальше — статистика.
Именно поэтому корреляция этих двух полей даёт антифроду больше, чем полный canvas-фингерпринт. Потому что их сложно подделать одновременно с IP, и потому что они меняются редко — пользователь не переключает таймзону каждые пять минут.
Как работает связка TZ + IP
Сервер видит два независимых источника: IP-адрес (откуда пришёл запрос) и часовой пояс (что заявил браузер). Если IP резолвится в Германию, а `timeZone` — `Asia/Novosibirsk`, это флаг. Одиночный — слабый. Систематический — сильный.
Антифрод берёт гео-базу (MaxMind, IP2Location, собственные данные) и сопоставляет: какой таймзон ожидается для этого IP. Допустим, диапазон `185.220.x.x` — это Hetzner, Германия. Ожидаемая таймзона: `Europe/Berlin`. Браузер говорит `Europe/Moscow`. Расхождение. Одно очко в копилку подозрительности.
Но это ещё не приговор. Реальный пользователь может сидеть под VPN. Поэтому система смотрит на корреляцию: если 8 из 10 аккаунтов, заходящих с немецких IP, заявляют московскую таймзону — это уже кластер. Антифрод связывает их в один граф и помечает.
Что именно собирает JS
Стандартный фингерпринт-скрипт на маркетплейсе собирает примерно такой набор:
```javascript
const fp = {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
tzOffset: new Date().getTimezoneOffset(),
language: navigator.language,
languages: navigator.languages,
locale: Intl.DateTimeFormat().resolvedOptions().locale,
currency: Intl.NumberFormat().resolvedOptions().currency,
dateFormat: new Intl.DateTimeFormat().format(new Date()),
numberFormat: new Intl.NumberFormat().format(1234567.89)
};
```
Обрати внимание на последние три. `dateFormat` для `en-US` даст `12/25/2024`, для `ru-RU` — `25.12.2024`. `numberFormat` для `de-DE` вернёт `1.234.567,89`, для `en-US` — `1,234,567.89`. Это косвенные подтверждения локали, которые не подделать простым переключением `navigator.language`.
Антифрод сравнивает: если `navigator.language` = `ru-RU`, но `dateFormat` показывает американский порядок — значит, язык выставлен вручную, а система осталась дефолтной. Классический след автоматизации или неаккуратной подмены.
Расхождение языка и таймзоны — главный маркер
Самая частая ошибка мультиаккаунтера — выставить русский язык, но забыть про таймзону. Или наоборот: поставить таймзону США, но оставить `ru-RU` в браузере.
Пример: сервер за прокси в Вирджинии, IP принадлежит DigitalOcean (`159.89.x.x`), ожидаемая таймзона `America/New_York`. Браузер заявляет `Europe/Moscow` и `ru-RU`. Три сигнала противоречат друг другу: IP говорит "США", таймзона — "Россия", язык — "Россия". Антифрод видит несоответствие IP↔TZ и записывает в профиль вес.
Дальше система проверяет историю. Если этот же fingerprint (или его варианты) появлялся на других аккаунтах — связь установлена. Даже если IP каждый раз новый. Потому что TZ + язык + формат даты + разрешение экрана + список шрифтов дают устойчивый кластер.
Как это выглядит в логах антифрода
Реальный пример структуры события, которое отправляется на бэкенд маркетплейса при логине:
```json
{
"session_id": "a7f3e9c2...",
"ip": "159.89.44.12",
"asn": "AS14061 DigitalOcean",
"geo_country": "US",
"geo_city": "Clifton",
"tz": "Europe/Moscow",
"tz_offset": -180,
"lang": "ru-RU",
"langs": ["ru-RU", "ru", "en-US", "en"],
"screen": "1920x1080",
"color_depth": 24,
"canvas_hash": "8f2a...",
"webgl_vendor": "Google Inc. (NVIDIA)",
"fonts_hash": "3c91...",
"login_ts": 1703421600
}
```
Антифрод-движок прогоняет это через правила. Одно из базовых: `geo_country != tz_country`. Если `US` против `Europe/Moscow` — вес +15. Если таких сессий с одного ASN больше десяти за сутки — вес +40. Если `canvas_hash` повторяется на разных аккаунтах — вес +60 и ручная проверка.
Порог срабатывания обычно 100-150 очков. Набирается быстро, если не следить за консистентностью.
Почему подмена TZ через DevTools не спасает
Многие думают: открою DevTools, переопределю `Intl.DateTimeFormat`, и всё. Не работает по двум причинам.
Первая: антифрод читает таймзону не только через JS. Он смотрит на HTTP-заголовки, на TLS-фингерпринт (JA3/JA4), на порядок заголовков. Некоторые системы используют `Date` header от клиента и сравнивают с серверным временем. Если клиент шлёт время в UTC+3, а IP в UTC-5 — расхождение видно без всякого JS.
Вторая: если ты переопределяешь `Intl` через прокси-объект, это ломает другие вызовы. `toLocaleString()` начинает возвращать не то, что ожидает страница. Маркетплейс может отправить тестовый вызов: создать дату и отформатировать её для известной локали, потом проверить результат. Подмена палится на несоответствии.
Вот пример теста, который может прогнать антифрод:
```javascript
const test = new Date('2024-06-15T12:00:00Z');
const expected = {
'Europe/Moscow': '15.06.2024, 15:00:00',
'America/New_York': '6/15/2024, 8:00:00 AM'
};
const actual = test.toLocaleString(navigator.language, {
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
});
```
Если `actual` не совпадает с ожидаемым для заявленной таймзоны — значит, что-то подменено. Скрипт помечает сессию.
Связка с поведенческими метриками
TZ и язык — статические сигналы. Их дополняют динамические. Антифрод смотрит на время активности: когда аккаунт заходит, когда совершает действия. Если таймзона заявлена как `America/Los_Angeles`, но все действия происходят с 9:00 до 18:00 по Москве — это несоответствие.
Пример: аккаунт с TZ `America/Chicago` логинится каждый день в 02:00 по местному времени (что соответствует 11:00 МСК). Активность идёт ровно 8 часов. Потом оффлайн. Для Чикаго это ночная смена, для Москвы — рабочий день. Антифрод складывает: TZ + паттерн активности + IP из дата-центра = автоматизированный мультиаккаунт.
Плюс скорость действий. Человек не может заполнить форму регистрации за 4 секунды. Бот может. Если время между загрузкой страницы и сабмитом меньше 6 секунд — вес растёт.
Кейс: сеть аккаунтов на одном прокси-пуле
Маркетплейс электроники, 40 аккаунтов, все заходят через пул из 12 прокси в Нидерландах. IP разные, ASN один — `AS49981 WorldStream`. Таймзона у всех `Europe/Amsterdam`, язык `nl-NL`. На первый взгляд — консистентно.
Но антифрод заметил: `canvas_hash` совпадает у 34 из 40 аккаунтов. `fonts_hash` — у 38. Разрешение экрана `1366x768` — у всех. И `tz_offset` показывает `-120`, хотя для `Europe/Amsterdam` в июне должно быть `-120` (CEST), а в декабре `-60` (CET). Аккаунты заходили в разные месяцы, но offset не менялся — значит, таймзона захардкожена в конфиге браузера, а не берётся из системы.
Результат: все 40 аккаунтов заблокированы в течение суток. Прокси-пул сожжён.
Кейс: несовпадение языка и валюты
Другой маркетплейс, категория "одежда". Аккаунт зарегистрирован на российский номер, платит картой РФ, но `navigator.language` = `en-US`, таймзона `America/New_York`, IP из Нью-Джерси. Вроде логично — человек в США.
Но `Intl.NumberFormat().resolvedOptions().currency` вернул `RUB`. Это значит, что в системе браузера валюта по умолчанию — рубль, хотя локаль американская. Антифрод пометил: локаль подменена, системные настройки остались российскими. Аккаунт ушёл на ручную проверку, потом в бан.
Причина: пользователь выставил язык и таймзону через расширение, но не тронул региональные настройки ОС. Антифрод читает их через `Intl` API и видит несоответствие.
Как выглядит корректная маскировка
Если нужно легитимно работать с нескольких аккаунтов (например, для тестирования), консистентность обязательна. Таймзона, язык, формат даты, валюта, IP — всё должно указывать на один регион.
Проверка через curl:
```bash
curl -s https://api.ipapi.com/159.89.44.12?access_key=KEY | jq '{country_code, timezone, currency}'
```
Ответ:
```json
{
"country_code": "US",
"timezone": "America/New_York",
"currency": "USD"
}
```
Дальше браузер должен показывать ровно эти значения. Не `Europe/Moscow`, не `RUB`. Если используешь антидетект-браузер, проверяй через такой скрипт:
```python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("about:blank")
result = page.evaluate("""() => ({
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
lang: navigator.language,
currency: Intl.NumberFormat().resolvedOptions().currency,
date: new Intl.DateTimeFormat().format(new Date()),
offset: new Date().getTimezoneOffset()
})""")
print(result)
browser.close()
```
Вывод должен совпадать с гео IP. Если `tz` = `America/New_York`, а `offset` = `-180` (МСК) — косяк. Для Нью-Йорка летом offset `-240`, зимой `-300`.
Что делать с прокси
Прокси — это первое, что проверяет антифрод. Дата-центровые IP палятся мгновенно: ASN принадлежит хостингу, а не residential-провайдеру. MaxMind отдаёт флаг `is_hosting: true`. Вес к подозрительности растёт сразу.
Для IPv6 ситуация сложнее. Residential IPv6-прокси реже, но их сложнее отследить, потому что пулы огромные. Один `/64` префикс может содержать миллиарды адресов. Антифрод не может заблокировать весь префикс — там живут реальные пользователи. Поэтому IPv6-прокси с правильной геолокацией и консистентной таймзоной проходят чаще, чем IPv4 из дата-центра.
Сервисы вроде lexic.ml дают IPv6-адреса с привязкой к региону. Если брать адрес в Германии — таймзона должна быть `Europe/Berlin`, язык `de-DE`, валюта `EUR`. Тогда связка IP↔TZ↔lang не вызывает вопросов. Но если тот же адрес использовать с `ru-RU` и `Europe/Moscow` — антифрод свяжет аккаунты по несоответствию быстрее, чем по IP.
Итоговая логика антифрода
Система не ищет "плохих". Она строит граф и ищет аномалии. Таймзона и язык — два узла, которые почти всегда коррелируют с IP. Если корреляция нарушена — сигнал. Если нарушение повторяется на разных аккаунтах — кластер. Если кластер совпадает с другими сигналами (canvas, fonts, поведение) — бан.
Обойти это можно только полной консистентностью. Не подменой одного поля, а согласованностью всех: IP, ASN, гео, таймзона, язык, локаль, валюта, формат даты, поведение. Как только одно звено выпадает — антифрод это видит. И запоминает.