Ваш PHP-код под прицелом: почему молчание — не золото
Представьте: вы запустили сайт, всё работает как часы, клиенты довольны. А в это время где-то в темном углу интернета скрипт-кидди вводит в вашу форму поиска волшебную строку ' OR 1=1 --. И база данных, словно послушный слуга, выдаёт ему все пароли пользователей. Знакомая история? К сожалению, для многих начинающих разработчиков — да.
Безопасность в PHP — это не скучная бюрократия, а реальная защита вашего времени, денег и репутации. Сегодня разберем двух главных врагов веб-приложений: SQL-инъекции и межсайтовый скриптинг (XSS). Вы узнаете, как их распознать, обезвредить и больше никогда не допускать в своем коде.
SQL-инъекции: когда пользователь становится хакером
SQL-инъекция — это атака, при которой злоумышленник «впрыскивает» вредоносный SQL-код через поля ввода. Если вы просто вставляете данные пользователя в запрос, считайте, что вы сами открыли дверь грабителю.
Как выглядит уязвимый код
Вот классический пример, который до сих пор встречается в старых учебниках и на живых сайтах:
<?php
// ОПАСНО! Никогда так не делайте
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);
?>
Что произойдёт, если в поле username ввести admin' --? Запрос превратится в:
SELECT * FROM users WHERE username = 'admin' --' AND password = ''
Двойной дефис -- комментирует остаток запроса. Вуаля — злоумышленник заходит как админ без пароля. А если ввести '; DROP TABLE users; --? Прощайте, пользователи.
Спасательный круг: подготовленные запросы (Prepared Statements)
Лучший способ защититься от SQL-инъекций — использовать подготовленные запросы через PDO или MySQLi. Они разделяют логику запроса и данные, поэтому вредоносный код никогда не выполнится.
Вот как выглядит безопасный код с PDO:
<?php
// БЕЗОПАСНО! Используем подготовленные запросы
$username = $_POST['username'];
$password = $_POST['password'];
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->execute(['username' => $username, 'password' => $password]);
$user = $stmt->fetch();
?>
Теперь даже если злоумышленник передаст admin' OR 1=1 --, PDO воспримет это как обычную строку, а не как часть SQL-команды. База данных скажет: «Ищем пользователя с таким странным именем». И не найдёт.
Почему PDO, а не mysql_*?
- Безопасность по умолчанию: PDO автоматически экранирует данные.
- Гибкость: поддерживает разные СУБД (MySQL, PostgreSQL, SQLite).
- Именованные плейсхолдеры: код становится читаемым и поддерживаемым.
- Объектно-ориентированный подход: современный стиль программирования.
Функции mysql_* удалили из PHP 7.0, и это к лучшему. Если вы всё ещё используете mysqli_real_escape_string — это лучше, чем ничего, но PDO всё равно надёжнее.
XSS-атаки: когда код прячется в комментариях
Межсайтовый скриптинг (XSS) — это внедрение JavaScript-кода на страницу через пользовательский ввод. Представьте: пользователь оставляет комментарий, а вместо текста пишет:
<script>alert('Ваш сайт взломан!');</script>
Если вы выводите это без обработки, каждый посетитель увидит всплывающее окно. А если вместо alert будет код, крадущий куки или перенаправляющий на фишинговый сайт?
Типы XSS-атак
- Reflected (отражённый): вредоносный код передаётся через URL (например, в параметре поиска).
- Stored (сохранённый): код сохраняется в базе данных (комментарии, сообщения форума).
- DOM-based: атака происходит на стороне клиента через манипуляции с DOM.
Как защититься: экранирование вывода
Золотое правило PHP-разработчика: никогда не доверяйте пользовательским данным. Фильтруйте на входе, экранируйте на выходе.
Для вывода данных в HTML используйте htmlspecialchars():
<?php
// Безопасный вывод
$comment = $_POST['comment']; // Допустим, уже прошёл валидацию
echo "<p>" . htmlspecialchars($comment, ENT_QUOTES, 'UTF-8') . "</p>";
?>
Параметры ENT_QUOTES экранирует и одинарные, и двойные кавычки. UTF-8 — обязательный параметр для корректной работы с кириллицей.
Продвинутая защита: Content Security Policy
Даже если вы забыли экранировать один параметр, Content Security Policy (CSP) может спасти ситуацию. Это HTTP-заголовок, который сообщает браузеру, откуда можно загружать скрипты и стили.
Пример настройки CSP в PHP:
<?php
header("Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com;");
?>
Теперь даже если злоумышленник внедрит <script>...</script>, браузер его не выполнит — только скрипты с вашего домена и доверенного CDN.
Практический чек-лист: делаем код неуязвимым
Чтобы не забыть ничего важного, вот пошаговая инструкция для каждого запроса и вывода:
1. Для работы с базой данных
- Используйте PDO или MySQLi с подготовленными запросами.
- Никогда не конкатенируйте пользовательские данные в SQL.
- Настройте режим ошибок PDO на исключения (
PDO::ERRMODE_EXCEPTION).
2. Для вывода данных
- Всегда используйте
htmlspecialchars()с параметрамиENT_QUOTESи