Урок 25: Безопасность в PHP (защита от XSS и CSRF)

На 25-ом уроке мы переходим к одной из важных тем в веб-разработке, это безопасность. Если до этого мы учились создавать функциональные приложения, то сейчас научимся защищать их от злоумышленников. Безопасность это не «опциональная фича», а обязательный слой вашего кода. Даже небольшая ошибка может привести к утечке данных, взлому сайта или потере доверия пользователей. В этом уроке разберем основные принципы безопасности, а также научимся защищаться от XSS и CSRF-атак.

Основные принципы безопасности в PHP

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

1. Валидация и санитизация данных

Валидация это проверка, что данные соответствуют ожидаемому формату (например, email содержит символ @). Санитизация это очистка данных от опасных символов или преобразование их в безопасный формат.

Пример:

// Валидация email
$email = $_POST['email'];
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    die("Некорректный email!");
}

// Санитизация строки
$username = $_POST['username'];
$clean_username = htmlspecialchars($username, ENT_QUOTES, 'UTF-8');

Правило:

  • Все данные от пользователя (GET, POST, COOKIE) считаются недоверенными, пока не доказано обратное.
  • Используйте встроенные функции PHP: filter_var()htmlspecialchars()strip_tags().

2. Защита от SQL-инъекций

SQL-инъекции это атаки, когда злоумышленник внедряет вредоносный SQL-код через поля ввода (например, формы логина).

Как защищаться:

  • Используйте подготовленные запросы (prepared statements) с PDO или MySQLi.

Пример с PDO:

$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();

Никогда не делайте так:

// Уязвимый код!
$sql = "SELECT * FROM users WHERE email = '$email'";
$result = mysqli_query($conn, $sql);

3. Используйте HTTPS

Все современные сайты должны работать по протоколу HTTPS. Он шифрует данные между клиентом и сервером, защищая их от перехвата.

Как внедрить:

  • Купите SSL-сертификат для домена.
  • Настройте редирект с HTTP на HTTPS в файле .htaccess:
apache
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

4. Работа с паролями

Хранить пароли в открытом виде — преступление. Используйте функции хеширования:

// Хеширование пароля
$password = 'user_password_123';
$hash = password_hash($password, PASSWORD_DEFAULT);

// Проверка пароля
if (password_verify($password, $hash)) {
    echo 'Пароль верный!';
}

5. Обновляйте ПО

Устаревшие версии PHP, библиотек или CMS (например, WordPress) содержат уязвимости. Регулярно обновляйте все компоненты вашего проекта.

6. Принцип минимальных привилегий

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

Защита от XSS (Cross-Site Scripting)

XSS это уязвимость, позволяющая злоумышленнику внедрить вредоносный JavaScript-код на страницу вашего сайта. Это может привести к краже куки-файлов, перенаправлению пользователей на фишинговые сайты или подмене контента.

Пример уязвимого кода:

// Уязвимый код: выводим данные без обработки
echo $_GET['search_query'];

Если злоумышленник передаст в search_query строку <script>alert('XSS!');</script>, на странице выполнится JavaScript.

Типы XSS-атак

  1. Reflected XSS — вредоносный скрипт встраивается в URL и выполняется сразу после перехода по ссылке.
  2. Stored XSS — скрипт сохраняется на сервере (например, в комментариях) и выполняется у всех пользователей.
  3. DOM-based XSS — атака происходит на стороне клиента через манипуляции с DOM.

Как защититься от XSS?

1. Санитизация выводимых данных

Все данные, выводимые в HTML, должны быть обработаны функцией htmlspecialchars().

Пример:

$user_comment = $_POST['comment'];
// Очищаем данные перед выводом
echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');

2. Использование заголовков Content Security Policy (CSP)

CSP позволяет ограничить источники скриптов, стилей и других ресурсов.

Пример заголовка:

header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'");

3. Фильтрация вводимых данных

Удаляйте опасные теги и атрибуты с помощью strip_tags() или библиотек вроде HTML Purifier.

Пример:

$user_bio = $_POST['bio'];
// Разрешаем только теги <b>, <i>, <p>
$clean_bio = strip_tags($user_bio, '<b><i><p>');

Практическая задача: Защита формы комментариев

Условие:
Создайте форму для комментариев, где пользователь вводит имя и текст. Защитите форму от XSS.

Решение:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = htmlspecialchars($_POST['name'], ENT_QUOTES, 'UTF-8');
    $comment = htmlspecialchars($_POST['comment'], ENT_QUOTES, 'UTF-8');
    
    // Сохраняем в базу данных или выводим
    echo "<div class='comment'><strong>$name</strong>: $comment</div>";
}
?>

<form method="POST">
    <input type="text" name="name" placeholder="Ваше имя">
    <textarea name="comment" placeholder="Ваш комментарий"></textarea>
    <button type="submit">Отправить</button>
</form>

Защита от CSRF (Cross-Site Request Forgery)

CSRF это атака, когда злоумышленник заставляет пользователя выполнить нежелательное действие на сайте, где он авторизован. Например, изменить пароль или сделать перевод денег.

Сценарий атаки:

  1. Пользователь авторизуется на сайте example.com.
  2. Злоумышленник отправляет ему ссылку на свой сайт.
  3. На сайте злоумышленника есть форма, которая автоматически отправляет запрос на example.com/change-password.
  4. Если сайт example.com не защищен от CSRF, запрос выполнится от имени пользователя.

Как защититься от CSRF?

1. CSRF-токены

Идея: генерировать уникальный токен для каждой формы и проверять его при отправке.

Шаги:

  1. При открытии формы генерируем токен и сохраняем его в сессии.
  2. Добавляем скрытое поле с токеном в форму.
  3. При отправке формы сравниваем токен из формы с токеном в сессии.

Пример:

session_start();

// Генерация токена
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// Форма
<form method="POST">
    <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
    <!-- Остальные поля -->
</form>

// Проверка токена
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
    die('Неверный CSRF-токен!');
}

2. Заголовок Referer Check

Проверяйте, что запрос пришел с вашего домена:

if (!isset($_SERVER['HTTP_REFERER']) || parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) !== 'yourdomain.com') {
    die('Недействительный источник запроса!');
}

Но учтите: Заголовок Referer может отсутствовать или быть подделанным. Используйте этот метод только как дополнение к CSRF-токенам.

Практическая задача: Защита формы изменения пароля

Условие:
Создайте форму для изменения пароля с CSRF-защитой.

Решение:

<?php
session_start();

// Генерация токена
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Проверка токена
    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
        die('Ошибка CSRF!');
    }
    
    // Обработка данных
    $new_password = $_POST['new_password'];
    // ... смена пароля ...
    echo 'Пароль успешно изменен!';
}
?>

<form method="POST">
    <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
    <input type="password" name="new_password" placeholder="Новый пароль">
    <button type="submit">Сменить пароль</button>
</form>

Сегодня мы разобрали ключевые аспекты безопасности в PHP:

  1. Основные принципы: валидация, санитизация, защита от SQL-инъекций, HTTPS.
  2. Защита от XSS: экранирование вывода, CSP, фильтрация ввода.
  3. Защита от CSRF: использование токенов, проверка Referer.

Безопасность это процесс, а не разовое действие. Всегда тестируйте свой код, используйте инструменты вроде OWASP ZAP для поиска уязвимостей и следите за обновлениями.

Хотите узнать больше? Переходите к следующим урокам: полный курс по PHP для начинающих. Там мы разберем работу с базами данных, авторизацию и создание REST API!