Защита сайта от ботов без упора на CAPTCHA: серверная валидация, rate limiting и step-up проверки

21.04.20267 мин чтения
Макар Кучеренко
Python-разработчикМакар Кучеренко

Если на сайте до сих пор вся защита от ботов сводится к одной CAPTCHA перед формой, проблема уже не в ботах, а в архитектуре. В 2026 году бот умеет открыть нормальный браузер, дождаться рендера, пройти часть клиентской логики, поменять IP и, если нужно, отдать challenge на solver-сервис. Поэтому инженерный вопрос звучит не так: "какую CAPTCHA поставить?", а так: "какие сигналы мы проверяем на сервере и что делаем, если запрос выглядит подозрительно".

У той же Cloudflare Turnstile серверная проверка обязательна: клиентский виджет сам по себе ничего не защищает. В документации прямо сказано, что токены можно подделать, они живут только 300 секунд и принимаются один раз. Если сервер не вызывает Siteverify, злоумышленник может отправить на ваш endpoint любую строку вместо токена. Это типичная ошибка интеграции.

Где именно ломается схема "поставили CAPTCHA и успокоились"

CAPTCHA обычно ставят в одной точке: логин, регистрация, форма обратной связи или checkout. Но атака редко живет в одной точке.

Вот что бот умеет на практике:

  • автоматизировать браузер через Playwright, Puppeteer или Selenium;
  • распределять запросы по residential proxy и ASN, чтобы не упираться в один IP;
  • отправлять challenge на внешний solver;
  • переиспользовать нормальный пользовательский поток, если backend проверяет только факт наличия токена, а не его валидность;
  • размазывать попытки по времени, чтобы не упереться в простой лимит N запросов в минуту.

Поэтому OWASP выделяет не один "бот-тип", а целый набор автоматизированных угроз. В реестре OWASP Automated Threats отдельно перечислены OAT-009 CAPTCHA Defeat и OAT-008 Credential Stuffing, который определяется как массовые логин-попытки для проверки украденных пар логин/пароль.

Из этого следует простой инженерный вывод: CAPTCHA можно оставить как один из сигналов, но нельзя делать её центром защиты.

Что должно происходить на сервере после challenge

Технически корректная схема выглядит так:

  1. Клиент получает токен challenge.
  2. Клиент отправляет токен вместе с действием: логин, регистрация, лид-форма, checkout.
  3. Backend валидирует токен на стороне сервера.
  4. Backend сверяет, что токен не просрочен, не переиспользован и относится к ожидаемому действию.
  5. Backend объединяет результат challenge с другими сигналами: IP, заголовки, частота попыток, fingerprint, репутация ASN, качество сессии.
  6. Только после этого сервер решает, что делать: пропустить запрос, попросить step-up, заморозить действие или отдать запрос на ручную проверку.

Cloudflare Turnstile в документации по серверной валидации отдельно пишет три вещи, которые важны в коде:

  • токен можно подделать, если вы не валидируете его на backend;
  • токен живет 5 минут;
  • токен одноразовый, и повторное использование должно падать с timeout-or-duplicate.

Это важно не как абстрактная рекомендация, а как конкретное правило интеграции. Если ваш endpoint принимает запрос только потому, что поле cf-turnstile-response вообще пришло, бот уже обошел защиту.

Минимальная реализация: как валидировать challenge правильно

Для формы, логина или регистрации минимальный серверный поток должен быть таким:

const response = await fetch(
  'https://challenges.cloudflare.com/turnstile/v0/siteverify',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      secret: process.env.TURNSTILE_SECRET,
      response: token,
      remoteip: clientIp
    })
  }
);

const result = await response.json();

if (!result.success) {
  return rejectRequest('challenge_failed', result['error-codes']);
}

Дальше полезно добавить ещё три проверки:

  • совпадает ли ожидаемое действие с тем, что вы ждете на endpoint;
  • совпадает ли hostname;
  • не пришел ли один и тот же токен дважды.

В рекомендациях Google reCAPTCHA по automated threats та же логика сформулирована через action и expectedAction: действие на странице и ожидаемое действие при серверной проверке должны совпадать. Если не совпадают, запрос нельзя считать нормальным.

Именно здесь много команд ошибаются. Challenge ставят на фронте, а backend не связывает токен с реальным действием. В итоге токен с регистрации можно попытаться подсунуть в другой сценарий.

Почему checkbox CAPTCHA ухудшает продукт и почти не помогает архитектуре

Если нужна живая, а не маркетинговая причина, она очень простая: checkbox challenge сам по себе добавляет трение, но не даёт достаточно контекста.

Google в best practices по automated threats прямо пишет, что checkbox keys увеличивают friction и могут бить по conversion rate. Там же в оптимальной схеме Google рекомендует не просто challenge, а score-based keys, action binding и отдельные сценарии для логина, checkout, account creation и других действий.

То есть даже поставщик CAPTCHA уже предлагает мыслить не виджетом, а риск-моделью:

  • для логина нужен один action;
  • для регистрации другой;
  • для checkout третий;
  • для account change четвертый.

Одинаковый challenge на все сценарии обычно означает, что защита поставлена "для галочки".

Что добавить кроме CAPTCHA: базовый anti-bot стек для веб-приложения

Ниже стек, который реально полезен разработке.

1. Action-specific rate limiting

Лимиты должны жить не на уровне "весь сайт", а на уровне конкретного действия.

Например:

  • /login — лимит по IP, login identifier и устройству;
  • /register — лимит по IP, email domain и fingerprint;
  • /contact — лимит по IP, user-agent и частоте похожих payload;
  • /checkout — лимит по аккаунту, корзине, платежному методу и IP.

Самая частая ошибка здесь — лимит только по IP. Для credential stuffing этого мало.

2. Step-up вместо блокировки всех подряд

Не каждый риск должен заканчиваться блоком.

Нормальный flow такой:

  • низкий риск — пропускаем без лишнего трения;
  • средний риск — включаем challenge или email verification;
  • высокий риск — просим MFA, замораживаем действие или режем сессию.

OWASP MFA Cheat Sheet прямо говорит, что MFA — лучшая защита от большинства password attacks, включая credential stuffing и password spraying.

3. Ограничение привилегий после регистрации

Даже если бот зарегистрировал аккаунт, это не значит, что он сразу должен получить полный доступ.

Хорошая практика:

  • задержка на публикацию контента;
  • лимиты на первые N сообщений или заявок;
  • ограничение на массовые API-вызовы;
  • отдельная модерация первых действий;
  • верификация email или телефона до получения чувствительных прав.

Это особенно полезно для форм, личных кабинетов, маркетплейсов и B2B-сервисов.

4. Honeypot и серверная валидация payload

Для лид-форм и простых контактных сценариев часто помогает не "сильнее challenge", а более умный backend:

  • скрытое поле honeypot;
  • проверка слишком быстрой отправки формы;
  • минимальное время заполнения;
  • отказ, если payload повторяется слишком часто;
  • нормализация телефона, email и URL до записи в CRM.

Это дешево внедряется и хорошо режет мусорный автоматизированный трафик.

5. Наблюдаемость, а не только блокировка

Если вы не пишете события, вы не защищаете систему, а просто надеетесь.

Минимум, который стоит логировать:

{
  "route": "/login",
  "action": "account_login",
  "ip": "client-ip",
  "asn": "asn-id",
  "fingerprint": "device-hash",
  "challenge_success": true,
  "challenge_errors": [],
  "rate_limit_bucket": "login:ip+email",
  "decision": "step_up",
  "latency_ms": 184
}

Это нужно, чтобы потом ответить на нормальные инженерные вопросы:

  • challenge вообще помогает или только мешает;
  • токены чаще падают из-за атак или из-за кривой интеграции;
  • какие маршруты атакуют сильнее;
  • где больше false positive;
  • какие сценарии ломают конверсию.

Cloudflare в аналитике Turnstile советует смотреть хотя бы три базовые метрики: Siteverify requests, Valid tokens и Invalid tokens. Большая доля invalid tokens может означать и ботов, и сломанную интеграцию.

Типичные ошибки интеграции, которые ломают защиту

Вот список, который чаще всего встречается в веб-проектах:

Challenge проверяется только на клиенте

Это самая грубая ошибка. Если backend не вызывает Siteverify, защита фиктивна.

Один и тот же challenge на все действия

Логин, регистрация и checkout — это разные рисковые сценарии. Их нельзя сваливать в один action и одну политику.

Нет проверки expectedAction

Если action с фронта не сверяется на сервере, challenge можно использовать не там, где он был выдан.

Нет отдельной стратегии для credential stuffing

OWASP описывает credential stuffing как массовую проверку украденных пар логин/пароль. Для такого сценария CAPTCHA без MFA и rate limit обычно только тормозит атаку, но не останавливает её.

Нет защиты от replay

Токен challenge нельзя принимать больше одного раза. Это не "дополнительная опция", а часть threat model.

Нет fallback-логики при отказе внешнего провайдера

Если у Cloudflare или другого провайдера challenge временная проблема, у backend должно быть понятное поведение:

  • fail closed для high-risk операций;
  • step-up или retry для среднего риска;
  • деградация без полной поломки UX для low-risk сценариев.

Практический anti-bot план на 2 недели

Если у вас сейчас только CAPTCHA, а нормального backend-контроля нет, разумный план выглядит так:

Неделя 1

  1. Разделить маршруты по риску: login, register, contact, checkout, password-reset.
  2. Включить серверную валидацию challenge для каждого чувствительного маршрута.
  3. Добавить action и серверную проверку ожидаемого действия.
  4. Ввести rate limiting не только по IP, но и по email/login/fingerprint.
  5. Завести логи по challenge_success, error-codes, decision, latency.

Неделя 2

  1. Включить step-up для среднего риска.
  2. Для логина добавить MFA или хотя бы подготовить rollout на high-risk логины.
  3. Для регистрации и форм добавить honeypot и delayed privileges.
  4. Сверить метрики: conversion, invalid tokens, false positive, spam rate.
  5. Прогнать ручной abuse-тест: replay token, подмена action, быстрая отправка формы, массовые попытки логина.

Если нужен не отдельный виджет, а именно серверная логика, контроль маршрутов и разбор аномалий по событиям, это уже задача на веб-разработку и прикладную anti-abuse архитектуру.

Источники для проверки

  1. Cloudflare Turnstile: Validate the token
  2. Cloudflare Turnstile: Token validation analytics
  3. Google reCAPTCHA: Best practices for protection from automated threats
  4. OWASP Automated Threats to Web Applications
  5. OWASP OAT-008 Credential Stuffing
  6. OWASP Multifactor Authentication Cheat Sheet

Вам также может быть интересно

Оставьте свои контакты — мы перезвоним, разберёмся в задаче и предложим оптимальный путь. За плечами более 350 проектов, каждый из которых мы запускали с индивидуального подхода. Гарантируем экспертную консультацию в рабочее время.