- Читать данные форм из суперглобальных массивов, проверять их на сервере и делать перенаправление после POST
- Предотвращать XSS с помощью
htmlspecialcharsи безопасно использовать сессии и cookie - Хранить пароли с помощью
password_hash, применять CSRF-токен и проверять загружаемые файлы
Форма обратной связи, страница входа, корзина покупок — «живая» часть сайта работает с данными, которые приходят от пользователей. А этим данным никогда нельзя доверять вслепую: кто-то ошибётся случайно, а кто-то сделает их вредоносными нарочно. В этом уроке ты изучишь веб-возможности PHP и основные способы защиты, которые должен знать каждый веб-разработчик.
Суперглобальные массивы
PHP помещает сведения о запросе в особые массивы. Они видны в любом месте кода, даже внутри функций, поэтому называются суперглобальными. Правило: запросы, которые только читают данные (поиск, фильтры), отправляют методом GET, а запросы, которые меняют данные (вход, заказ, комментарий), — методом POST.
| Массив | Что содержит |
|---|---|
$_GET | параметры в адресе: search.php?q=php → $_GET['q'] |
$_POST | поля формы, отправленной с method="post" |
$_SERVER | сведения о запросе, например $_SERVER['REQUEST_METHOD'] |
$_COOKIE, $_SESSION | cookie, присланные браузером, и данные сессии |
$_FILES | загруженные файлы |
XSS и htmlspecialchars
Представь, что в гостевой книге кто-то вместо имени написал тег <script>. Если вывести его на страницу как есть, этот скрипт выполнится в браузере каждого посетителя: он может украсть cookie или изменить страницу. Такая атака называется XSS (cross-site scripting). Защита проста: экранируй каждый текст от пользователя функцией htmlspecialchars, прежде чем вставлять его в HTML.
<p>Hello, <?= $_GET['name'] ?>!</p><p>Hello, <?= htmlspecialchars($_GET['name'] ?? '', ENT_QUOTES, 'UTF-8') ?>!</p>?name=<script>...</script> страница слева выполнит скрипт, а справа — покажет его как обычный текст.<?php
$input = '<script>alert("hacked")</script>';
echo htmlspecialchars($input), "\n";
function e(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
echo e("Tom & Jerry's \"show\""), "\n";<script>alert("hacked")</script> Tom & Jerry's "show"
Обработка и проверка формы
Проверки в браузере вроде required нужны только для удобства — их легко обойти. Настоящая проверка выполняется на сервере. После успешного POST перенаправь пользователя на другую страницу с помощью header('Location: ...') и вызови exit. Это шаблон PRG (Post/Redirect/Get): при обновлении страницы форма не отправляется повторно.
<?php
session_start();
$errors = [];
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (mb_strlen($name) < 2) {
$errors[] = 'Name is too short';
}
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
$errors[] = 'Email is not valid';
}
if ($errors === []) {
// save the message to the database here
$_SESSION['flash'] = "Thanks, $name!";
header('Location: /contact.php');
exit;
}
}
// below: the HTML form shows $errors and refills the fields with e($name)Для проверки удобна функция filter_var: для корректного значения она возвращает само значение (при необходимости приведённое к нужному типу), а для неверного — false. Её можно попробовать и в терминале:
<?php
var_dump(filter_var("aysel@example.com", FILTER_VALIDATE_EMAIL));
var_dump(filter_var("aysel@", FILTER_VALIDATE_EMAIL));
var_dump(filter_var("42", FILTER_VALIDATE_INT));
var_dump(filter_var("200", FILTER_VALIDATE_INT, [
"options" => ["min_range" => 1, "max_range" => 120],
]));string(17) "aysel@example.com" bool(false) int(42) bool(false)
Сессии и cookie
HTTP «не помнит»: каждый запрос ничего не знает о предыдущем. Узнать пользователя можно двумя способами. Cookie — небольшие данные, которые хранятся в браузере и отправляются обратно на сервер с каждым запросом. Сессия хранит данные на сервере, а браузер получает только идентификатор сессии (cookie PHPSESSID). session_start() и setcookie() отправляют HTTP-заголовки, поэтому их нужно вызывать до того, как на страницу будет выведен любой текст.
<?php
session_start();
$_SESSION['views'] = ($_SESSION['views'] ?? 0) + 1;
setcookie('theme', 'dark', [
'expires' => time() + 60 * 60 * 24 * 30,
'path' => '/',
'httponly' => true,
'samesite' => 'Lax',
]);
$theme = $_COOKIE['theme'] ?? 'light';
echo "Views this session: {$_SESSION['views']}, theme: $theme";Views this session: 1, theme: light. После обновления: 2, theme: dark — cookie приходит только со следующим запросом.httponlyскрывает cookie от JavaScript — даже при XSS её труднее украсть. На сайте с HTTPS добавь ещё'secure' => true.- При входе пользователя вызови
session_regenerate_id(true), затем запиши$_SESSION['user_id']— старый идентификатор станет бесполезным. - При выходе пиши
$_SESSION = [];иsession_destroy();. - Не храни секреты в cookie: пользователь может их увидеть и изменить. Секретные данные остаются в сессии.
Загрузка файлов
У формы, отправляющей файл, должны быть method="post" и enctype="multipart/form-data". PHP кладёт файл во временную папку, а сведения о нём записывает в $_FILES. Проверь размер и настоящий тип файла, дай ему новое случайное имя и только потом перемести его функцией move_uploaded_file.
<?php
$file = $_FILES['photo'] ?? null;
if ($file && $file['error'] === UPLOAD_ERR_OK) {
if ($file['size'] > 2 * 1024 * 1024) {
exit('File is larger than 2 MB');
}
$type = mime_content_type($file['tmp_name']);
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png'];
if (!isset($allowed[$type])) {
exit('Only JPG and PNG images are allowed');
}
$newName = bin2hex(random_bytes(8)) . '.' . $allowed[$type];
move_uploaded_file($file['tmp_name'], __DIR__ . '/uploads/' . $newName);
echo 'Uploaded!';
}<input type="file" name="photo">Пароли и CSRF
Никогда не храни пароли в открытом виде или с помощью md5 и sha1 — такие хеши взламываются очень быстро. password_hash хеширует пароль специально медленным алгоритмом (сейчас это bcrypt) со случайной «солью» (salt), а password_verify проверяет его. При каждом вызове хеш получается другим, поэтому сравнивать хеши через === нельзя. В базе данных отведи под хеш столбец VARCHAR(255).
<?php
$hash = password_hash("secret123", PASSWORD_DEFAULT);
var_dump(password_verify("secret123", $hash));
var_dump(password_verify("Secret123", $hash));
var_dump($hash === password_hash("secret123", PASSWORD_DEFAULT));bool(true) bool(false) bool(false)
При атаке CSRF (cross-site request forgery) чужой сайт отправляет форму на твой сайт от имени браузера пользователя — например, чтобы сменить его пароль. Браузер автоматически добавляет cookie сессии, поэтому запрос выглядит «законным». Для защиты в сессии создают случайный токен, вставляют его в каждую POST-форму скрытым полем и проверяют во входящем запросе.
<?php
session_start();
// 1. create a token once per session
$_SESSION['csrf'] ??= bin2hex(random_bytes(32));
// 2. every POST form gets a hidden field named csrf with this value
// 3. check the token when a form is submitted
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$sent = $_POST['csrf'] ?? '';
if (!hash_equals($_SESSION['csrf'], $sent)) {
http_response_code(403);
exit('Invalid CSRF token');
}
// the form is safe to process
}<input type="hidden" name="csrf" value="<?= e($_SESSION['csrf']) ?>">Главное
$_GETхранит параметры адреса,$_POST— поля формы; операции, меняющие данные, отправляют черезPOST.- Экранируй любой текст от пользователя через
htmlspecialcharsперед выводом в HTML — это предотвращает XSS. - Проверка выполняется на сервере (
filter_var, типы); после успешногоPOSTперенаправляй черезheader('Location: ...')иexit. - Данные сессии хранятся на сервере, а cookie — в браузере;
session_start()вызывают до любого вывода. - Пароли защищают
password_hash/password_verify, формы — CSRF-токен, а загрузки — проверка размера и настоящего типа.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.