Avatar ·

Как правильно очищать пользовательский ввод в PHP от XSS и SQL-инъекций?

Всем привет! Я начинающий PHP-разработчик, пишу свой первый сайт — простую гостевую книгу. Пользователи оставляют имя и сообщение, которые я сохраняю в MySQL и потом вывожу на страницу. Сейчас мой код выглядит примерно так: ```php // Сохранение в БД $name = $_POST['name']; $message = $_POST['message']; $sql = "INSERT INTO comments (name, message) VALUES ('$name', '$message')"; mysqli_query($conn, $sql); // Вывод на страницу echo "

$name: $message

"; ``` Я заметил, что если ввести в поле имя что-то вроде ``, то при выводе сообщения появляется всплывающее окно. А если ввести `' OR '1'='1`, то запрос вроде бы ломается. Я читал, что нужно "санировать" (sanitize) ввод, но не понимаю, как это делать правильно. В интернете куча советов — одни говорят использовать `htmlspecialchars()`, другие — `strip_tags()`, третьи — `mysqli_real_escape_string()`. А что из этого действительно нужно? Или нужно всё вместе? Подскажите, пожалуйста, какой правильный подход к очистке пользовательского ввода в PHP в 2024 году? Хочется понять принцип, а не просто скопировать кусок кода. Заранее спасибо за помощь! <<>>

Правильный подход к очистке пользовательского ввода в PHP

Привет! Ты столкнулся с двумя разными типами уязвимостей, и их нужно обрабатывать по-разному. Главный принцип безопасности: никогда не доверяй пользовательскому вводу. Но "очистка" — это не одна универсальная функция, а контекстно-зависимые действия.

1. SQL-инъекции (защита при работе с БД)

Твой пример с ' OR '1'='1 — это классическая SQL-инъекция. Злоумышленник может не только сломать запрос, но и удалить таблицы или получить доступ к данным.

Неправильно: использовать mysqli_real_escape_string(). Хотя эта функция экранирует спецсимволы, её легко забыть применить к одному из полей. Кроме того, она не защищает от всех атак (например, при использовании в LIKE-выражениях).

Правильно: использовать подготовленные запросы (prepared statements) через PDO или MySQLi. Они отделяют SQL-код от данных.

// Правильный способ через PDO
$stmt = $pdo->prepare("INSERT INTO comments (name, message) VALUES (:name, :message)");
$stmt->execute([
    'name' => $_POST['name'],
    'message' => $_POST['message']
]);

Или через MySQLi:

$stmt = $conn->prepare("INSERT INTO comments (name, message) VALUES (?, ?)");
$stmt->bind_param("ss", $_POST['name'], $_POST['message']);
$stmt->execute();

Подготовленные запросы автоматически экранируют данные и гарантируют, что ввод никогда не будет интерпретирован как SQL-код.

2. XSS-атаки (защита при выводе на страницу)

Твой пример с <script>alert('hack')</script> — это XSS (межсайтовый скриптинг). Злоумышленник может украсть куки, перенаправить пользователя на фишинговый сайт или изменить содержимое страницы.

Неправильно: использовать strip_tags(). Она вырезает все теги, но если тебе нужно разрешить некоторые (например, <b> или <i>), то эта функция не подойдёт.

Правильно: использовать htmlspecialchars() с правильными параметрами. Она преобразует спецсимволы HTML в их безопасные эквиваленты (< → &lt;, > → &gt; и т.д.).

// Правильный способ вывода
$safe_name = htmlspecialchars($_POST['name'], ENT_QUOTES | ENT_HTML5, 'UTF-8');
$safe_message = htmlspecialchars($_POST['message'], ENT_QUOTES | ENT_HTML5, 'UTF-8');
echo "<p><b>$safe_name</b>: $safe_message</p>";

Важные параметры:

  • ENT_QUOTES — экранирует и одинарные, и двойные кавычки
  • ENT_HTML5 — использует современные HTML5-сущности
  • 'UTF-8' — кодировка (обязательно указывай, иначе возможны проблемы с многобайтовыми символами)

3. Дополнительные рекомендации

  • Валидация — проверяй, что ввод соответствует ожидаемому формату (email, число, URL). Используй filter_var() или регулярные выражения.
  • Никогда не используй htmlspecialchars() для защиты от SQL-инъекций — это не работает.
  • Никогда не используй mysqli_real_escape_string() как единственную защиту — используй подготовленные запросы.
  • Content Security Policy (CSP) — настрой HTTP-заголовок, который запрещает выполнение inline-скриптов на странице.

Запомни золотое правило: Фильтруй ввод, экранируй вывод. Для каждого контекста (SQL, HTML, URL, JSON) — свой метод экранирования.

<<>>

Альтернативный подход: использование библиотек для санитации

Если ты не хочешь писать всю логику безопасности вручную, можно использовать готовые библиотеки. Они уже учитывают множество edge-кейсов и регулярно обновляются.

Рекомендуемые инструменты:

  • HTML Purifier — мощная библиотека для очистки HTML от XSS. Позволяет разрешить безопасные теги (например, <b>, <i>, <a>) и удалить опасные. Идеально подходит для форумов или комментариев, где нужен rich-text.
  • PHP Filters — встроенные функции filter_input() и filter_var() для валидации и санитации. Например, filter_var($email, FILTER_VALIDATE_EMAIL) или filter_var($url, FILTER_SANITIZE_URL).
  • Symfony/Validator или Laravel Validation — если используешь фреймворки, их встроенные валидаторы уже включают защиту от XSS и SQL-инъекций.

Пример с фильтрами:

// Валидация email
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if ($email === false) {
    die('Некорректный email');
}

// Санитация строки (удаление тегов, кодирование спецсимволов)
$name = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING);

Но помни: фильтры не заменяют подготовленные запросы и htmlspecialchars(). Они лишь облегчают валидацию. Для полной безопасности используй комбинацию: подготовленные запросы + htmlspecialchars() на выводе + валидацию через фильтры.

Если проект небольшой, ручной подход из первого ответа — лучший выбор. Если проект растёт, подумай о фреймворке (Laravel, Symfony), где безопасность уже встроена на уровне ядра.