Защита форм в веб-приложении: рабочие приёмы
Одна HTML-форма не защищает ни от CSRF, ни от мусорных данных, ни от перебора. Разбираем практический набор мер: токены, SameSite, проверку Origin, валидацию, ограничения частоты и безопасную обработку ошибок.

Форма оплаты, смены email или удаления аккаунта может принять запрос от чужого сайта, если приложение не проверяет источник и не использует CSRF-защиту. Для рабочей схемы нужны несколько слоёв: токен запроса, ограничения cookie, серверная валидация, лимит частоты и аккуратные ошибки. Ни один из них не заменяет остальные. Ниже — практика для веб-приложения с серверными сессиями; примеры проверки токена приведены для PHP 8.2+.
Сначала разделите угрозы
«Защитить форму» — не одна задача. У разных атак разные причины и исправления.
- CSRF — злоумышленник заставляет браузер уже вошедшего пользователя отправить запрос на ваш сайт. Cookie браузер может приложить автоматически.
- XSS — чужой скрипт попадает в страницу и выполняется в браузере. CSRF-токен сам по себе от этого не спасает.
- Мусорные или опасные данные — сервер принимает неожиданный тип, размер или значение поля.
- Перебор — форма входа, восстановления пароля или промокода принимает слишком много попыток.
CSRF-токен — случайное значение, которое сервер связывает с сессией или конкретной формой и проверяет при отправке запроса. Знание cookie не должно автоматически означать знание токена.
Проверяйте не только то, что пользователь отправил, но и почему сервер должен этому доверять: откуда пришёл запрос, в какой сессии он создан и сколько таких запросов уже было.
Четыре слоя защиты и их цена
В небольшом приложении обычно достаточно сочетания первого, второго и третьего слоя. Для входа, восстановления пароля и платежных операций добавляйте ограничение частоты.
| Мера | Что закрывает | Цена и ограничение |
|---|---|---|
| CSRF-токен | Подделку запроса из чужого сайта | Нужно добавлять токен во все изменяющие состояние запросы и корректно обновлять формы |
| SameSite cookie | Часть межсайтовых запросов с cookie | Зависит от сценария входа, редиректов и переходов между доменами; это не единственная защита |
| Origin или Referer | Запросы с неожиданного источника | Заголовок может отсутствовать или меняться из-за политики приватности, поэтому нельзя полагаться только на него |
| Валидация и лимиты | Мусорные данные, дорогие операции и перебор | Требует правил для каждого поля, хранилища счётчиков и настройки лимитов |
CSRF: токен должен проверяться на сервере
Скрытое поле в HTML — только переносчик значения. Защита появляется в момент, когда сервер сравнивает полученный токен с ожидаемым значением из сессии. Проверяйте CSRF для POST, PUT, PATCH и DELETE, если запрос меняет данные. Для простого HTML-примера на PHP 8.2+ генерация токена может выглядеть так:
<?php
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$csrfToken = $_SESSION['csrf_token'];
?>
<form method="post" action="/profile/email">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8') ?>">
<label>
Новый email
<input type="email" name="email" required maxlength="254">
</label>
<button type="submit">Сохранить</button>
</form>
В атрибуте HTML значение нужно экранировать: это отдельная защита от внедрения разметки, а не проверка CSRF. На сервере сравнивайте токен безопасным сравнением и не показывайте пользователю ожидаемое значение.
<?php
session_start();
$receivedToken = $_POST['csrf_token'] ?? '';
$sessionToken = $_SESSION['csrf_token'] ?? '';
if ($sessionToken === '' || $receivedToken === '' ||
!hash_equals($sessionToken, $receivedToken)) {
http_response_code(403);
exit('Forbidden');
}
$email = trim((string)($_POST['email'] ?? ''));
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($email) > 254) {
http_response_code(422);
exit('Invalid email');
}
// Здесь — проверка прав и сохранение email в базе данных.
После успешной операции можно заменить токен, если действие особенно чувствительное. Для обычных форм допустим один токен на сессию, если он создаётся криптографически стойким генератором и не попадает в URL, логи или сообщения об ошибках.
Cookie и проверка источника
Для сессионной cookie включите как минимум Secure и HttpOnly. Атрибут SameSite=Lax часто подходит обычному сайту с авторизацией: cookie отправляется в ограниченном наборе межсайтовых сценариев, но не во всех. SameSite=Strict строже, однако может ухудшить сценарии входа после перехода с внешнего сайта. SameSite=None требует Secure и нужен только для обоснованных кросс-сайтовых сценариев.
Настройка зависит от сервера и фреймворка, поэтому не копируйте чужой ключ конфигурации вслепую. Проверьте итоговый заголовок Set-Cookie в инструментах разработчика. В нём должны быть ожидаемые флаги, а сессия не должна передаваться по HTTP.
Дополнительный слой — проверка Origin, а при его отсутствии аккуратная проверка Referer. Разрешите только свои точные схемы и хосты, например https://example.com, а не суффикс вроде example.com.attacker.test. Запрос без обоих заголовков нельзя автоматически считать безопасным для операции смены пароля или выплаты: выберите явную политику для клиентов, которые действительно их не отправляют.
Валидация: браузеру не доверяем
Атрибуты required, type="email" и maxlength улучшают интерфейс, но обходятся прямым HTTP-запросом. Сервер должен отдельно проверить наличие, тип, длину, формат, допустимый диапазон и права на операцию.
- Ограничивайте размер тела запроса и длину каждого поля до значения, которое реально нужно бизнес-операции.
- Проверяйте идентификаторы как идентификаторы, а не как произвольные строки. Не подставляйте пользовательский ввод в SQL — используйте параметризованные запросы.
- Для перечислений принимайте только значения из известного списка. Не передавайте имя поля или сортировки из запроса напрямую в SQL.
- HTML-экранирование делайте при выводе в конкретный контекст: HTML, атрибут, JavaScript и URL требуют разных правил.
- Ошибку показывайте рядом с полем, но не возвращайте введённый секрет, пароль или полный токен.
Ограничение частоты без ложного чувства защиты
CSRF не остановит бота, который отправляет корректные запросы из собственного клиента. Для входа и восстановления пароля ограничивайте попытки по нескольким признакам: IP, идентификатору аккаунта и, где уместно, устройству или сессии. Один только IP создаёт проблемы в офисах, мобильных сетях и за общим NAT.
Лимит храните на сервере или в общем хранилище, если приложение работает в нескольких экземплярах. После превышения возвращайте понятный ответ с кодом 429 Too Many Requests, но не сообщайте, существует ли указанный email. Для дорогих операций добавьте задержку, очередь или подтверждение после повторных неудач. CAPTCHA — дополнительный барьер, а не замена токену, валидации и лимитам.
Что ломается на практике
Форма загружается из кеша
Страница с персональным CSRF-токеном не должна попадать в общий публичный кеш. Иначе один пользователь может получить HTML с чужим токеном, а сервер закономерно отклонит отправку. Кешируйте общие части страницы отдельно или выдавайте токен через защищённый запрос, если архитектура этого требует.
SPA отправляет JSON
Токен можно передать в заголовке, но сервер должен проверять его так же строго, как скрытое поле. Проверьте CORS: разрешённый произвольный origin вместе с credentials фактически расширяет круг сайтов, способных инициировать запросы.
Прокси меняет схему и хост
Проверка Origin может ломаться за reverse proxy, если приложение неверно определяет внешний HTTPS и домен. Перед включением строгой проверки зафиксируйте список доверенных прокси и проверьте реальные заголовки на тестовом стенде. Не принимайте заголовок вроде X-Forwarded-Host от любого клиента как доказательство безопасного источника.
Проверка есть только в JavaScript
JavaScript отключается, заменяется собственным запросом или вообще не используется. Клиентская проверка нужна для удобства, серверная — для безопасности и целостности данных.
Как выбрать минимальный набор
Для обычной формы изменения профиля начните с серверного CSRF-токена, безопасной cookie, серверной валидации и корректного экранирования вывода. Для платежа, удаления аккаунта и смены пароля добавьте повторную аутентификацию или подтверждение намерения, проверку источника и аудит событий. Для входа и восстановления пароля обязательны лимиты, нейтральные сообщения об ошибках и контроль утечек в логах.
После внедрения составьте таблицу всех endpoint, которые меняют состояние: метод, требуемая авторизация, CSRF-проверка, лимит, максимальный размер тела, журналирование и код ответа при отказе. Затем проверьте каждый endpoint прямым запросом без токена, с просроченной сессией, с превышенным размером поля и с большим числом попыток.
Что делать дальше
Сначала включите защиту на формах входа, восстановления пароля, смены email и удаления данных — именно там цена ошибки выше всего. Затем поставьте интеграционные тесты: запрос без токена должен получать отказ, неверный источник — не проходить строгую операцию, а слишком частые попытки — получать 429. В проде следите за долей ответов 403, 422 и 429, а также за повторяющимися ошибками валидации: это быстрее покажет атаку или поломку клиента, чем просмотр отдельных логов.
Проверка рекламы на риски: Что в макете вызовет вопросы ФАС: превосходные степени, сравнения, обещания результата, маркировка.
Спросить за 40 ₽Источники: OWASP Cross-Site Request Forgery Prevention Cheat Sheet, OWASP Input Validation Cheat Sheet
Частые вопросы
Достаточно ли поставить SameSite=Lax и не добавлять CSRF-токен?
Нужно ли защищать CSRF-токеном GET-формы и ссылки?
Почему сервер отклоняет форму после долгого простоя страницы?
Можно ли проверять только заголовок Origin?
Что делать, если форма работает в SPA и отправляет JSON?
Спасёт ли CAPTCHA от перебора формы?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.