Безопасность
Сайт доступен всему интернету, и рано или поздно кто-нибудь попробует его взломать. Большинство взломов используют одну и ту же ошибку: программа доверяет данным, которые прислал пользователь. Главное правило — любые данные извне считаются опасными, пока не проверены.
XSS и экранирование
Если вывести на страницу текст от пользователя как есть, в него можно вставить скрипт, который выполнится у других посетителей. Это называется XSS. Функция htmlspecialchars() превращает опасные символы в безвредные:
<?php
$comment = '<script>alert("Взлом")</script>';
echo htmlspecialchars($comment);Браузер покажет текст скрипта буквами, но не выполнит его. Экранируйте всё, что выводите в HTML.
SQL-инъекции
Если склеить запрос к базе из строк, пользователь сможет изменить его смысл. Защита — подготовленные запросы из урока о базе данных:
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)');
$pdo->exec("INSERT INTO users (email) VALUES ('anna@example.com')");
$email = "' OR '1'='1";
$statement = $pdo->prepare('SELECT COUNT(*) FROM users WHERE email = ?');
$statement->execute([$email]);
echo $statement->fetchColumn(); // 0Значение передаётся отдельно от запроса, поэтому попытка взлома ищется как обычная строка и ничего не находит.
Хеширование паролей
Пароли нельзя хранить в открытом виде: при утечке базы они достанутся злоумышленнику. Хранят хеш — необратимый отпечаток пароля:
<?php
$hash = password_hash('секретный-пароль', PASSWORD_DEFAULT);
var_dump(password_verify('секретный-пароль', $hash)); // bool(true)
var_dump(password_verify('другой-пароль', $hash)); // bool(false)password_hash() каждый раз даёт разный результат для одного и того же пароля — так и должно быть. Проверяет пароль только password_verify().
CSRF-токен
Чужой сайт может незаметно отправить форму от имени вошедшего пользователя. Чтобы отличить свою форму от чужой, в неё добавляют случайный токен и сверяют его при обработке:
<?php
session_start();
$_SESSION['csrf'] ??= bin2hex(random_bytes(32));
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) {
http_response_code(419);
exit('Неверный токен');
}
}В форму токен кладут скрытым полем: <input type="hidden" name="csrf" value="...">.
Проверка входных данных
Проверяйте тип и формат всего, что приходит от пользователя. Для этого есть функция filter_var():
<?php
var_dump(filter_var('anna@example.com', FILTER_VALIDATE_EMAIL) !== false); // bool(true)
var_dump(filter_var('42', FILTER_VALIDATE_INT)); // int(42)
var_dump(filter_var('сорок два', FILTER_VALIDATE_INT)); // bool(false)Чек-лист
| Угроза | Защита |
|---|---|
| XSS | htmlspecialchars() при выводе |
| SQL-инъекция | Подготовленные запросы |
| Кража паролей | password_hash() и password_verify() |
| CSRF | Токен в каждой форме |
| Перехват данных | HTTPS на всём сайте |
Частая ошибка
Проверка в браузере — атрибут required, JavaScript — нужна для удобства, а не для защиты. Запрос можно отправить в обход формы, поэтому все проверки обязательно повторяют на сервере.