Каждый PHP-разработчик рано или поздно сталкивается с необходимостью защитить свой сайт от взлома. И если вы думаете, что вас это не коснётся, потому что ваш проект маленький и никому не нужен, — зря. Хакерские боты сканируют интернет круглосуточно и ищут любые лазейки. В этой статье разберём две самые популярные уязвимости — SQL-инъекции и XSS — и научимся от них защищаться. Будет много примеров кода и практических советов.

Digital-студия WNDER

Почему безопасность — это не скучно, а жизненно важно

Представьте, что ваш сайт — это дом. Вы можете поставить самую красивую дверь, но если замок хлипкий, воры войдут без проблем. SQL-инъекции и XSS — это как раз те самые хлипкие замки. Они позволяют злоумышленникам украсть данные, подменить контент или даже захватить контроль над всем сайтом. И самое обидное: часто дыры появляются из-за банальной невнимательности. Давайте посмотрим, как их закрыть.

SQL-инъекции: когда запросы выходят из-под контроля

SQL-инъекция — это когда пользователь через форму ввода может вставить кусок SQL-кода, который выполнится на сервере. Классический пример — форма логина. Если вы пишете запрос так:

$query = "SELECT * FROM users WHERE login = '" . $_POST['login'] . "' AND password = '" . $_POST['password'] . "'";

то злоумышленник может ввести в поле логина что-то вроде ' OR '1'='1, и запрос превратится в:

SELECT * FROM users WHERE login = '' OR '1'='1' AND password = ''

Условие '1'='1' всегда истинно, поэтому сервер пустит злоумышленника без пароля. И это только цветочки. Через SQL-инъекцию можно вытащить всю базу данных, удалить таблицы или выполнить произвольные команды на сервере.

Как защититься от SQL-инъекций

Главное правило: никогда не подставляйте пользовательские данные в SQL-запрос напрямую. Вместо этого используйте подготовленные выражения (prepared statements). В PHP это делается через PDO или MySQLi. Вот пример на PDO:

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

Здесь данные передаются отдельно от запроса, и база сама их экранирует. Это надёжно и просто.

Если вы используете MySQLi, то код будет таким:

$mysqli = new mysqli('localhost', 'user', 'pass', 'test');
$stmt = $mysqli->prepare('SELECT * FROM users WHERE login = ? AND password = ?');
$stmt->bind_param('ss', $_POST['login'], $_POST['password']);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();

Обратите внимание: в MySQLi мы используем знаки вопроса и метод bind_param, где указываем типы данных (s — строка, i — целое число и т.д.).

Правило: Всегда используйте подготовленные выражения для любых запросов, где есть пользовательские данные. Даже если вы «на 100% уверены» в безопасности — не рискуйте.

Ещё один важный момент: не забывайте про экранирование, если по каким-то причинам не можете использовать подготовленные выражения. Для этого есть функция mysqli_real_escape_string() или PDO::quote(). Но это менее надёжно, чем prepared statements.

XSS-атаки: когда ваш сайт стреляет в посетителей

XSS (Cross-Site Scripting) — это внедрение вредоносного JavaScript-кода на страницы вашего сайта. Представьте, что злоумышленник оставляет комментарий, в котором вместо текста пишет:

<script>alert('Взлом!');</script>

Если вы выводите комментарии без обработки, то каждый посетитель увидит всплывающее окно. Но это безобидный пример. Настоящие хакеры могут украсть cookies, перенаправить пользователя на фишинговый сайт или даже выполнить действия от его имени.

XSS бывает трёх типов: хранимый (когда скрипт сохраняется в базе), отражённый (когда скрипт приходит из параметров URL и сразу отображается) и DOM-based (когда уязвимость в JavaScript на клиенте). Нас больше интересуют первые два, так как они связаны с серверным кодом на PHP.

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

Основной метод — экранирование вывода. Всегда преобразуйте специальные символы в HTML-сущности перед тем, как показать данные пользователю. В PHP для этого есть функция htmlspecialchars().

$comment = $_POST['comment'];
echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');

Параметр ENT_QUOTES заставляет функцию преобразовывать как двойные, так и одинарные кавычки. А указание кодировки UTF-8 защищает от некоторых обходов.

Если вы выводите данные в атрибутах HTML, например в value поля ввода, то тоже используйте htmlspecialchars(). А для URL-параметров лучше применять urlencode().

Вот пример безопасного вывода комментария:

// Плохо:
echo "<div>" . $comment . "</div>";

// Хорошо:
echo "<div>" . htmlspecialchars($comment, ENT_QUOTES, 'UTF-8') . "</div>";

Также стоит помнить о контексте. Если вы вставляете данные в JavaScript-код, то нужно использовать json_encode() для безопасной передачи строк. Например:

$name = $_GET['name'];
echo "<script>var userName = " . json_encode($name) . ";</script>";

Это защитит от внедрения скриптов через переменную.

Лайфхак: Создайте вспомогательную функцию для экранирования, чтобы не забывать про неё. Например:
function e($string) {
    return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}
И используйте её везде, где выводите данные.

Другие важные меры безопасности

SQL-инъекции и XSS — не единственные угрозы. Вот ещё несколько советов, которые сделают ваш PHP-сайт крепче:

  • Хешируйте пароли. Используйте password_hash() и password_verify(). Никогда не храните пароли в открытом виде или в MD5.
  • Ограничивайте права базы данных. Не используйте пользователя root для соединения из PHP. Создайте отдельного пользователя с минимальными правами.
  • Валидируйте входные данные. Проверяйте, что пришло от пользователя, на соответствие ожидаемому формату (числа, email и т.д.).
  • Используйте CSRF-токены. Для защиты от межсайтовой подделки запросов.
  • Обновляйте PHP и библиотеки. Устаревшие версии содержат известные уязвимости.

Кстати, о валидации. Часто разработчики путают валидацию и санитизацию. Валидация проверяет, что данные соответствуют правилам (например, email содержит @). Санитизация очищает данные от нежелательных символов. Для безопасности нужно и то, и другое, но экранирование вывода — обязательно.

Сравнение методов защиты

Чтобы вы могли быстро выбрать подходящий инструмент, вот таблица с основными методами защиты и их применением.

Уязвимость Метод защиты Пример кода
SQL-инъекция Подготовленные выражения $pdo->prepare()
XSS Экранирование вывода htmlspecialchars()
CSRF Токены $_SESSION['token']
Слабые пароли Хеширование password_hash()

Как видите, арсенал у PHP-разработчика большой. Главное — применять его системно.

Что в итоге

Безопасность — это не разовое действие, а привычка. Всегда используйте подготовленные выражения для SQL-запросов и экранируйте вывод через htmlspecialchars(). Не доверяйте пользовательским данным, даже если они пришли от «проверенного» источника. И помните: лучше потратить лишние пять минут на защиту, чем потом восстанавливать сайт после взлома.

Начните с малого: проверьте свой код на наличие уязвимостей уже сегодня. А если что-то осталось непонятным — перечитайте примеры и закрепите практикой.

Студия WNDER