Как антифрод Stripe выявляет прокси по TLS JA3/JA4 отпечатку при оплате картой
Содержание
- Что вообще видит Stripe на TLS-уровне
- Почему обычный прокси палится сразу
- Как выглядит JA3 на практике
- JA4 — что изменилось
- Что делает прокси-провайдер
- Пример: проверка собственного JA3
- Почему одного JA3 недостаточно
- HTTP/2 fingerprint — следующий слой
- Как это выглядит в логах Stripe
- Что делать, если нужна автоматизация
- Почему IPv6 меняет расклад
- Ошибки, которые убивают анонимность
- Итог
Что вообще видит Stripe на TLS-уровне
Когда браузер клиента идёт на `api.stripe.com` или на хостнутую страницу оплаты, первым делом поднимается TLS-соединение. До того как улетит хоть один HTTP-заголовок, до того как JavaScript успеет что-то собрать, Stripe уже получает ClientHello. Это первое сообщение рукопожатия содержит кучу метаданных: версию TLS, список поддерживаемых шифров, набор расширений, эллиптические кривые, форматы точек, ALPN-протоколы, длины всего перечисленного.
Из этого набора считается хеш — JA3 (MD5 от строки с шифрами, расширениями, кривыми и т.д.) или его более новый вариант JA4. Хеш стабилен для конкретной реализации TLS-стека. Chrome 120 с определённым набором флагов даёт один JA3, Firefox 121 — другой, curl 8.5 — третий, Go-клиент — четвёртый. Python `requests` с urllib3 — вообще отдельная история.
Антифрод Stripe (Radar) собирает эти отпечатки и сопоставляет с тем, что заявляет клиент. Если User-Agent говорит «Chrome 120 на Windows», а JA3 соответствует curl или Go — это сигнал. Не приговор, но сигнал, который складывается с десятком других.
Почему обычный прокси палится сразу
Прокси сам по себе не меняет TLS-рукопожатие. SOCKS5-прокси просто туннелирует TCP-поток, TLS-сессия поднимается между клиентом и Stripe напрямую. Значит JA3 остаётся клиентским. Это нормально, пока клиент — реальный браузер.
Проблема начинается, когда через прокси гонят автоматизацию. Selenium с headless Chrome даёт JA3, отличный от обычного Chrome — из-за отличий в сборке BoringSSL и набора включённых расширений. Puppeteer с `--disable-blink-features` тоже меняет картину. А если это `curl` или `python-requests` через HTTP-прокси — JA3 будет вообще не браузерным.
Stripe видит: с одного IP идёт поток платежей, у которых JA3 = python-requests/2.31. При этом User-Agent подделан под Chrome. Расхождение между заявленным и реальным стеком — классический маркер фрода.
Как выглядит JA3 на практике
Возьму реальный пример. Chrome 120 на Linux:
```
771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0
```
JA3 = `cd08e31494f9531f560d64c695473da9`. Это MD5 от строки выше.
curl 8.5 (OpenSSL 3.2):
```
771,4866-4867-4865-49196-49200-49195-49199-52393-52392-49188-49192-49162-49172-49187-49191-49161-49171-52394-52393,0-11-10-35-22-23-13-43-45-51-21,29-23-30-25-24,0-1-2
```
JA3 = `1f6c3b6f...` — совсем другой хеш.
Python requests с urllib3 2.x через OpenSSL:
```
771,49195-49199-49196-49200-52393-52392-49161-49171-49162-49172-156-157-47-53,0-11-10-35-13-43-45-51,29-23-24-25,0
```
JA3 = `3b5074b1b5d032e5620f69f9f700ff0e`. Ещё один вариант.
Stripe держит базу таких отпечатков. Совпадение с известным браузерным JA3 — плюс к доверию. Совпадение с библиотечным — минус.
JA4 — что изменилось
JA4 появился в 2023 как ответ на слабости JA3. Основные отличия:
- вместо MD5 используется SHA-256 с усечением;
- учитывается порядок расширений и их сортировка;
- отдельно кодируется ALPN и SNI;
- есть JA4H (HTTP), JA4S (server), JA4X (X.509).
Формат JA4: `t13d1516h2_8daaf6152771_b186095e22b6`. Первая часть — версия TLS, тип шифра, количество расширений, ALPN. Вторая — хеш шифров. Третья — хеш расширений.
Для антифрода JA4 удобнее тем, что позволяет отличать не только стек, но и версию библиотеки. Chrome 120 и Chrome 121 могут дать разные JA4 при одинаковом JA3. Это даёт более точную привязку.
Что делает прокси-провайдер
Тут начинается интересное. Прокси, который просто перекидывает TCP, ничего не меняет в JA3. Прокси с TLS-терминацией (MITM) подменяет отпечаток на свой — и это обычно хуже, потому что сертификат придётся подсовывать клиенту, а Stripe видит сертификат прокси-ЦС.
Качественные прокси-сервисы для скрейпинга и автоматизации сейчас поднимают JA3-мимикрию. Это когда клиент не сам устанавливает TLS, а отдаёт запрос прокси, а прокси поднимает соединение с нужным отпечатком. Библиотеки вроде `curl-impersonate`, `tls-client` (Go), `cycleTLS` умеют воспроизводить JA3 Chrome или Firefox на уровне рукопожатия.
Схема выглядит так:
```
client -> proxy (JA3 spoof) -> Stripe
```
Клиент отправляет HTTP-запрос в открытом виде (или через CONNECT), прокси сам делает TLS-рукопожатие с отпечатком, идентичным Chrome 120. Stripe видит JA3 Chrome и не может отличить от настоящего браузера по этому параметру.
На практике с IPv6-прокси вроде тех, что крутятся через lexic.ml, это работает так: клиент идёт на прокси по IPv6, прокси делает исходящее соединение с подставленным JA3. Для Stripe источник — IPv6-адрес из пула, отпечаток — браузерный.
Пример: проверка собственного JA3
Чтобы понять, что видит Stripe, надо сначала посмотреть на свой отпечаток. Простой способ — через публичные эндпоинты:
```bash
curl -s https://tls.peet.ws/api/all | jq '.tls.ja3_hash, .tls.ja4'
```
Вывод покажет ваш JA3 и JA4. Если запустить это из Python:
```python
import requests
r = requests.get("https://tls.peet.ws/api/all", timeout=10)
data = r.json()
print("JA3:", data["tls"]["ja3_hash"])
print("JA4:", data["tls"]["ja4"])
print("User-Agent:", data["http_version"])
```
Сравните с тем, что выдаёт браузер. Если разница есть — Stripe её тоже увидит.
Почему одного JA3 недостаточно
Stripe Radar — это не только TLS-фингерпринт. Это совокупность:
- JA3/JA4;
- HTTP/2 fingerprint (SETTINGS-фрейм, порядок заголовков, приоритеты);
- TCP/IP fingerprint (TTL, window size, MSS, опции);
- canvas/WebGL fingerprint из JS;
- поведенческие метрики (скорость заполнения формы, движение мыши);
- история IP (сколько аккаунтов с него платило, были ли чарджбеки).
JA3 — один из сигналов. Если он совпадает с браузерным, но HTTP/2-отпечаток библиотечный — палево. Если оба совпадают, но canvas пустой — тоже. Если всё совпадает, но IP числится в датацентровом диапазоне — вопрос.
Именно поэтому прокси с одной только JA3-мимикрией не спасает. Нужна полная эмуляция стека.
HTTP/2 fingerprint — следующий слой
После TLS идёт HTTP/2. Клиент отправляет SETTINGS-фрейм с параметрами: `HEADER_TABLE_SIZE`, `ENABLE_PUSH`, `MAX_CONCURRENT_STREAMS`, `INITIAL_WINDOW_SIZE`, `MAX_FRAME_SIZE`, `MAX_HEADER_LIST_SIZE`. Порядок и значения этих параметров уникальны для реализации.
Chrome отправляет:
```
SETTINGS: 1:65536, 2:0, 4:6291456, 6:262144
WINDOW_UPDATE: 15663105
PRIORITY: stream 3, weight 200, dep 0
```
curl отправляет:
```
SETTINGS: 3:100, 4:1073741824
WINDOW_UPDATE: 1073741824
```
Разница видна сразу. Stripe собирает HTTP/2 fingerprint так же, как JA3 — хеширует параметры. Несоответствие между JA3 (Chrome) и HTTP/2 (curl) — красный флаг.
Как это выглядит в логах Stripe
Точных логов Stripe никто не публикует, но по косвенным данным и по документации Radar можно восстановить картину. При каждой оплате формируется риск-скор. В него входят:
| Параметр | Вес (условно) | Что проверяется |
|---|---|---|
| JA3/JA4 | средний | совпадение с заявленным UA |
| HTTP/2 fingerprint | средний | согласованность с TLS |
| IP-репутация | высокий | датацентр, история, ASN |
| Canvas/WebGL | высокий | наличие, энтропия |
| Поведение | высокий | тайминги, движения |
| 3DS | высокий | прохождение аутентификации |
Если JA3 палит, но всё остальное чисто — скорее всего пройдёт. Если JA3 чистый, но IP датацентровый и canvas пустой — блок.
Что делать, если нужна автоматизация
Тут два пути. Первый — полная эмуляция браузера через Playwright с реальным Chrome, а не headless. Это дорого по ресурсам, но даёт настоящие отпечатки. Второй — TLS-мимикрия плюс HTTP/2-мимикрия плюс резидентные IP.
Пример на Python с `tls-client`:
```python
from tls_client import Session
session = Session(client_identifier="chrome_120")
response = session.get(
"https://api.stripe.com/v1/tokens",
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."}
)
print(response.status_code)
```
`client_identifier="chrome_120"` подставляет JA3, JA4 и HTTP/2-параметры Chrome 120. Это уже не просто прокси, а полноценная мимикрия.
Но даже это не гарантия. Stripe смотрит на IP. Резидентный IPv4 с историей — ок. Датацентровый IPv6 — подозрительно. Хотя IPv6 из residential-пулов сейчас набирает вес.
Почему IPv6 меняет расклад
IPv6-адресов много. Пул /64 — это 18 квинтиллионов адресов. Прокси-провайдеры выдают клиентам адреса из своих /48 или /56, и каждый адрес выглядит как отдельный хост. Stripe не может просто забанить диапазон — это отрежет легитимных пользователей.
Но у IPv6 есть особенность: многие датацентры анонсируют крупные блоки, и ASN сразу выдаёт принадлежность. Если JA3 идеальный, а IP из ASN хостера — риск-скор растёт. Если IP из residential-пула — падает.
Комбинация «браузерный JA3 + резидентный IPv6 + чистый HTTP/2» — это то, что сейчас проходит. Но гонка продолжается: Stripe обновляет детекторы, прокси-провайдеры обновляют мимикрию.
Ошибки, которые убивают анонимность
Типичные грабли. Первая — использовать `requests` с подменой User-Agent. JA3 выдаст Python с потрохами. Вторая — гонять через HTTP-прокси без TLS-мимикрии. Прокси видит открытый CONNECT, а Stripe видит клиентский JA3. Третья — смешивать отпечатки: JA3 от Chrome, HTTP/2 от Firefox. Детектор ловит несоответствие.
Ещё одна — IPv6-адрес с обратным DNS, указывающим на хостинг. Stripe резолвит PTR и видит `hosted-by-datacenter.example`. Всё, минус к доверию.
И последняя — не менять отпечаток между сессиями. Если с одного IP идёт 500 платежей с одинаковым JA3 и одинаковым HTTP/2 — это бот. Настоящий браузер варьирует хотя бы версии расширений.
Итог
JA3 и JA4 — это первые слои детекта, но не единственные. Stripe собирает отпечатки на TLS, HTTP/2, TCP и JS-уровнях, сопоставляет их между собой и с заявленным клиентом. Прокси без мимикрии палится на первом же рукопожатии. Прокси с мимикрией проходит TLS, но может спалиться на HTTP/2 или IP-репутации.
Рабочая схема сегодня — это полный стек: браузерный JA3/JA4, согласованный HTTP/2 fingerprint, резидентный IPv6 из чистого ASN, реальный canvas и поведенческие метрики. Всё, что короче, рано или поздно упирается в блок.