HTML / Безопасность
Как HTML-форма отправляет данные и как безопасно обработать их на сервере
Путь данных от элементов формы до PHP: action, method, имена полей, GET и POST, валидация, CSRF и безопасный вывод ошибок.
Содержание статьи
HTML собирает данные и описывает способ отправки, но сам их не сохраняет. После нажатия кнопки браузер формирует HTTP-запрос, а серверная программа проверяет значения и выполняет нужное действие: отправляет письмо, создаёт запись или возвращает ошибки.
Разберём весь путь на форме обратной связи.
Из чего состоит форма
<form action="/feedback" method="post"> <div> <label for="name">Имя</label> <input id="name" name="name" type="text" autocomplete="name" required> </div> <div> <label for="email">Email</label> <input id="email" name="email" type="email" autocomplete="email" required> </div> <div> <label for="message">Сообщение</label> <textarea id="message" name="message" rows="6" maxlength="3000" required></textarea> </div> <button type="submit">Отправить</button></form>Главные атрибуты <form>:
action— адрес обработчика;method— HTTP-метод, обычноgetилиpost;enctype— формат тела запроса; для загрузки файлов нуженmultipart/form-data.
У каждого отправляемого поля должен быть name. id связывает элемент с <label>, но не определяет имя параметра.
Какие элементы попадают в запрос
Браузер отправляет только успешные элементы управления. В частности:
- поле должно иметь
name; disabled-поле не отправляется;- у группы radio отправляется выбранное значение;
- у checkbox отправляется значение только при установленном флажке;
- нажатая submit-кнопка может передать собственные
nameиvalue.
<label> <input type="checkbox" name="agreement" value="yes" required> Я согласен с правилами</label>Сервер не должен ожидать, что agreement существует в каждом запросе: клиент может снять атрибут required или отправить запрос вручную.
GET или POST
GET подходит для операций чтения: поиска, фильтрации и пагинации. Параметры видны в URL, ссылку можно сохранить и передать.
<form action="/search" method="get" role="search"> <label for="query">Поиск по сайту</label> <input id="query" name="q" type="search"> <button type="submit">Найти</button></form>Результат может открыться по адресу /search?q=html.
POST используют, когда запрос изменяет данные или передаёт большой объём. Значения не появляются в адресной строке, но это не шифрование: конфиденциальность в пути обеспечивает HTTPS. Пароли и токены нельзя писать в URL.
Клиентская валидация
Атрибуты HTML помогают найти часть ошибок до отправки:
<input id="email" name="email" type="email" maxlength="254" autocomplete="email" required>Можно использовать min, max, minlength, maxlength и pattern, но не следует превращать каждое правило в сложное регулярное выражение. Например, type="email" понятнее самодельного паттерна для адреса.
Клиентская проверка служит удобству. Сервер обязан заново проверить тип, длину, формат и допустимые значения.
Безопасный обработчик на PHP
Ниже показан минимальный учебный обработчик. Он не отправляет письмо и не пишет в базу, а демонстрирует проверку запроса, CSRF-токена и данных.
<?phpdeclare(strict_types=1);session_start();if (!isset($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32));}$errors = [];$name = '';$email = '';$message = '';if ($_SERVER['REQUEST_METHOD'] === 'POST') { $token = is_string($_POST['csrf_token'] ?? null) ? $_POST['csrf_token'] : ''; if (!hash_equals($_SESSION['csrf_token'], $token)) { http_response_code(403); exit('Не удалось проверить запрос. Обновите страницу.'); } $name = is_string($_POST['name'] ?? null) ? trim($_POST['name']) : ''; $email = is_string($_POST['email'] ?? null) ? trim($_POST['email']) : ''; $message = is_string($_POST['message'] ?? null) ? trim($_POST['message']) : ''; if ($name === '' || mb_strlen($name) > 100) { $errors['name'] = 'Укажите имя длиной до 100 символов.'; } if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) { $errors['email'] = 'Укажите корректный email.'; } if ($message === '' || mb_strlen($message) > 3000) { $errors['message'] = 'Введите сообщение длиной до 3000 символов.'; } if ($errors === []) { // Здесь вызывают отдельный сервис сохранения или отправки. $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); header('Location: /feedback/success', true, 303); exit; }}is_string() защищает от неожиданного массива вместо строки. trim() нормализует пробелы по краям, но не делает значение безопасным автоматически. Каждое поле проходит отдельную проверку.
Добавление CSRF-токена в форму
Изменяющий POST-запрос должен быть связан с пользовательской сессией:
<form action="/feedback" method="post"> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES, 'UTF-8') ?>" > <!-- Поля формы --></form>Скрытое поле не является доверенным: сервер сравнивает его с токеном сессии через hash_equals().
Как вернуть значения и ошибки
При повторном показе формы недоверенные данные экранируют в момент вывода:
<label for="name">Имя</label><input id="name" name="name" type="text" value="<?= htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" <?= isset($errors['name']) ? 'aria-invalid="true" aria-describedby="name-error"' : '' ?>><?php if (isset($errors['name'])): ?> <p id="name-error"><?= htmlspecialchars($errors['name'], ENT_QUOTES, 'UTF-8') ?></p><?php endif; ?>Для <textarea> значение помещается между тегами и тоже экранируется. Не вставляйте пользовательскую строку через innerHTML и не храните «уже экранированные» данные: контекст вывода может измениться.
Сохранение в базе
Если данные попадают в SQL, используйте подготовленный запрос. Экранирование HTML не защищает от SQL-инъекции.
$statement = $pdo->prepare( 'INSERT INTO feedback (name, email, message) VALUES (:name, :email, :message)');$statement->execute([ 'name' => $name, 'email' => $email, 'message' => $message,]);Валидация, SQL-параметры и HTML-экранирование решают разные задачи, поэтому ни один слой не заменяет другой.
Что проверить перед публикацией
- форма работает с клавиатуры, у всех полей есть
<label>; - сервер принимает только ожидаемый метод;
- значения проверяются по типу, длине, формату и allowlist;
- POST защищён CSRF-токеном;
- SQL использует параметры;
- пользовательский текст экранируется при выводе;
- после успешного POST выполняется redirect, чтобы обновление страницы не повторяло отправку;
- публичная ошибка не раскрывает внутренние пути, запросы и настройки базы.
HTML-форма — только начало цепочки. Надёжный сценарий появляется, когда разметка, серверная валидация, защита запроса и понятная обратная связь работают вместе.