Как защитить форму от ботов и спама

Защита формы — это совокупность мер, а не один волшебный метод. Цель: не сделать атаку невозможной (что невозможно), а сделать её дороже выгоды атакующего. Каждый метод снижает вероятность успеха атаки на свой процент, а вместе они делают её экономически нецелесообразной. Здесь мы собрали проверенные методы с примерами кода.

Задержка перед повторным запросом

Атакующему выгодна возможность выполнить максимальное количество запросов за минимальное время с одного устройства. Повторный запрос кода подтверждения с одного IP адреса или на один номер следует разрешать не ранее, чем через 90-120 секунд. А лучше - увеличивать это время с каждым повторным запросом. Эту проверку следует осуществлять не только в интерфейсе браузера, но и на серверной стороне.

$ip = $_SERVER['REMOTE_ADDR'];
$phone = $_POST['phone'];
$isLimited = apcu_fetch($ip) || apcu_fetch($phone);
apcu_store($ip, 1, 90);
apcu_store($phone, 1, 90);
if ($isLimited) {
    // deny
}

Как улучшить: использовать browser fingerprinting [1] [2] и exponential backoff [1] [2].

Ограничение количества запросов с одного IP

IPv4 адреса в мире закончились, а их аренда стоит денег, что увеличивает стоимость ресурсов атакующего. Адреса IPv6 же практически бесплатны и безграничны, потому поддержку IPv6 лучше отключить.

Стоимость аренды 1 IPv4 адреса ~200₽, а отправки SMS — ~10₽, что делает невыгодным атаку при <20 запросов с одного IP. Пример ограничения для 5 запросов с одного IP в сутки:

$ip = $_SERVER['REMOTE_ADDR'];
$hits = apcu_fetch($ip) ? apcu_inc($ip) : intval(apcu_store($ip, 1, 86400));
if ($hits > 5) {
    // deny
}

Как улучшить: автоматически добавлять IP-адреса в запрещенные на уровне фаервола или веб-сервера [1] [2] [3] [4]. В примитивном варианте это может выглядеть так:

if ($hits > 5) {
    @fwrite(fopen('/var/log/spam.log', 'a'), "deny $ip;\n");
}

// nginx.conf:
server {
    include /var/log/spam.log;
    allow all;

// crontab:
*/5 * * * * /etc/init.d/nginx reload

Если включены дорогие направления трафика, стоит ограничивать при повторных отправках не одиночный IP, а подсеть /24.

Ограничение количества запросов на один номер

При отправке сообщения через api.greensms.ru повторный запрос идёт альтернативным маршрутом, чтобы гарантировать доставку. Последующие сообщения будут направлены теми же маршрутами, поэтому, если предположить, что они по каким-то причинам не были получены, дальнейшие повторы нецелесообразны.

Пример ограничения для 5 запросов на один номер в сутки:

$phone = $_POST['phone'];
$hits = apcu_fetch($phone) ? apcu_inc($phone) : intval(apcu_store($phone, 1, 86400));
if ($hits > 5) {
    // deny
}

Как улучшить: начиная с 3-го запроса использовать код в номере, входящий звонок, код голосом, VK, WhatsApp, Viber вместо SMS; запретить отправку в страны, в которых нет ваших пользователей.

Использование CSRF-токена

Наличие в форме уникального одноразового токена и его проверка на серверной стороне немного усложняет задачу атакующему, поскольку гарантируют, что запрос отправлен лишь после предварительной загрузки формы. Детали на примере Laravel можно почерпнуть из документации.

Как улучшить: проверять, отправлен ли запрос посредством AJAX [1], использовать SameSite Cookie [1]

Невидимая капча

reCAPTCHA v3 является “скрытой” формой проверки, не требующей со стороны пользователя решения задач или ввода текста. В результате работы сервис возвращает значение в диапазоне от 0 до 1, означающее вероятность того, что пользователь — человек.

На практике, значения от 0,3 могут присваиваться легитимным пользователям, потому для полноценной фильтрации могут быть использованы лишь значения <0,3. В остальных случаях необходима комбинация с другими методами, возможность пользователю подтвердить другим способом, что он не робот.

Помимо этого, поскольку метод использует browser fingerprinting — наблюдаются ботнеты, способные стабильно показывать результат в 0,9. Всплеск активности на скриншоте — нелегитимный трафик с оценкой 0,9.

reCAPTCHA stats

Для сайтов в РФ интеграция с Google может влиять на скорость загрузки страницы, блокировать работу формы при блокировках внешних сервисов, нарушать закон о персональных данных. В этой связи стоит использовать капчу от Яндекс, желательно в режиме “невидимой капчи”.

Как улучшить: дополнить отображением reCAPTCHA v2 в сомнительных случаях, например при оценке в диапазоне 0,3-0,7 или повторных действиях.

Блокировка нежелательного трафика

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

Можно ограничить доступ для известных ботов и инструментов разработки на основе заголовков User-Agent и Referer.

В API и личном кабинете GREENSMS вы можете самостоятельно устанавливать ограничения по странам для каждого модуля.

Как улучшить: блокировать IP адреса дата-центров и хостинг-провайдеров.

Сервисы фильтрации трафика

Cloudflare, Qrator, DDoS-Guard предлагают не только решения по защите от DDoS, но и от ботов. Но и это не панацея.

Ограничить количество сообщений за день

Установить лимит можно как на стороне вашего сервера, так и в личном кабинете GREENSMS или API, где можно указать независимые суточные лимиты по модулям.

Лимиты по модулям в личном кабинете GREENSMS

Мониторинг и триггеры

Стоит настроить уведомления при превышении заданных порогов отправки или наличие существенного количество блокировок. Такие алерты можно настроить, как в консоли Яндекса, так и на вашем сервере (например с отправкой уведомления в Telegram). Дополнительно, в нашем личном кабинете можно настроить уведомления об аномальном трафике, который превышает средние значения за предыдущую неделю.

Подключить методы верификации

Злоумышленники могут использовать сервисы по получению входящих SMS в автоматическом режиме для обхода верификации действия пользователя. Защититься от этого помогало использование услуги Flash Call, о чём мы рассказывали на Хабре. В последнее время начали появляться сервисы, которые помогают обходить и этот канал, более надежным становится Обратный звонок, когда пользователю необходимо совершить исходящий звонок на специальный номер без оплаты вызова.