Как предотвратить SQL-инъекции в PHP? Безопасная работа с MySQL
Основная проблема и правильное решение
Вы абсолютно правы: ваш код уязвим для SQL-инъекций. Использование mysqli_real_escape_string — это устаревший и ненадёжный метод. Современный и правильный подход — использование подготовленных запросов (prepared statements) с параметрами. Это полностью разделяет SQL-логику и пользовательские данные, делая инъекции невозможными.
Решение с использованием MySQLi (объектно-ориентированный стиль)
// Создаём подключение к БД
$mysqli = new mysqli("localhost", "user", "password", "database");
// Проверяем соединение
if ($mysqli->connect_error) {
die("Connection failed: " . $mysqli->connect_error);
}
// Получаем данные из формы
$username = $_POST['username'];
$password = $_POST['password'];
// Подготавливаем запрос с плейсхолдерами (?)
$stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
// Привязываем параметры: "ss" означает два строковых параметра
$stmt->bind_param("ss", $username, $password);
// Выполняем запрос
$stmt->execute();
// Получаем результат
$result = $stmt->get_result();
if ($result->num_rows > 0) {
echo "Добро пожаловать, " . htmlspecialchars($username) . "!";
} else {
echo "Неверный логин или пароль";
}
// Закрываем запрос и соединение
$stmt->close();
$mysqli->close();
Решение с использованием PDO (рекомендуется)
PDO — более гибкий и современный интерфейс, который поддерживает разные СУБД.
try {
// Подключение к БД
$pdo = new PDO("mysql:host=localhost;dbname=database;charset=utf8", "user", "password");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Подготовка запроса с именованными плейсхолдерами
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
// Привязываем параметры
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);
// Выполняем
$stmt->execute();
// Проверяем результат
if ($stmt->rowCount() > 0) {
echo "Добро пожаловать, " . htmlspecialchars($username) . "!";
} else {
echo "Неверный логин или пароль";
}
} catch (PDOException $e) {
die("Ошибка: " . $e->getMessage());
}
Дополнительные меры безопасности
- Храните пароли в хешированном виде — используйте
password_hash()для сохранения иpassword_verify()для проверки пароля. Никогда не храните пароли в открытом виде. - Всегда экранируйте вывод — используйте
htmlspecialchars()при отображении пользовательских данных в HTML, чтобы предотвратить XSS-атаки. - Используйте правильную кодировку — при подключении указывайте charset=utf8 или utf8mb4.
- Ограничьте права БД — для веб-приложения используйте пользователя MySQL с минимально необходимыми правами (SELECT, INSERT, UPDATE, DELETE).
- Не доверяйте пользовательским данным — всегда валидируйте и фильтруйте входные данные (например, проверяйте, что email действительно является email).
Чего НЕ стоит делать
- Не используйте
mysqli_real_escape_string()— это ненадёжно и легко забыть применить. - Не конкатенируйте строки запроса с пользовательскими данными — это главная причина уязвимостей.
- Не используйте устаревшее расширение
mysql_*— оно удалено из PHP 7.
Подготовленные запросы — это золотой стандарт безопасности PHP. Они просты в использовании и полностью защищают от SQL-инъекций. Начните с PDO — это современный и удобный инструмент.
<<Альтернативный подход: использование ORM и Query Builder
Если вы работаете с крупными проектами или фреймворками (Laravel, Symfony, Yii), рекомендую использовать встроенные ORM (Object-Relational Mapping) или Query Builder. Они автоматически используют подготовленные запросы и обеспечивают безопасность "из коробки".
Пример с использованием Laravel Eloquent
// В контроллере
$user = User::where('username', $request->input('username'))
->where('password', $request->input('password'))
->first();
if ($user) {
return "Добро пожаловать, " . e($user->username) . "!";
} else {
return "Неверный логин или пароль";
}
Пример с использованием Doctrine (Symfony)
$user = $entityManager->getRepository(User::class)->findOneBy([
'username' => $username,
'password' => $password
]);
Почему это удобно?
- Автоматическая защита — все запросы проходят через подготовленные выражения.
- Чистый код — не нужно писать SQL-запросы вручную.
- Переносимость — легко сменить СУБД (MySQL, PostgreSQL, SQLite).
- Дополнительные возможности — миграции, связи между таблицами, кэширование.
Однако для небольших проектов или обучения PDO — отличный выбор. Главное — никогда не подставляйте данные напрямую в SQL-запросы.