Avatar ·

Как предотвратить SQL-инъекции в PHP? Безопасная работа с MySQL

Всем привет. Я начинающий PHP-разработчик, и у меня возникла серьёзная проблема с безопасностью. Пишу простой сайт для регистрации пользователей, но не уверен, что мой код защищён от взлома. Вот пример моего кода для авторизации: ```php $username = $_POST['username']; $password = $_POST['password']; $query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; $result = mysqli_query($connection, $query); if (mysqli_num_rows($result) > 0) { echo "Добро пожаловать, $username!"; } else { echo "Неверный логин или пароль"; } ``` Я слышал, что такой код уязвим для SQL-инъекций. Например, если ввести `' OR '1'='1` в поле username, то можно обойти проверку пароля. Как мне правильно защитить своё приложение? Нужно ли экранировать кавычки с помощью `mysqli_real_escape_string`? Или есть более современные способы? Желательно с примерами кода, чтобы я мог сразу применить на практике. Заранее спасибо за помощь! <<>>

Основная проблема и правильное решение

Вы абсолютно правы: ваш код уязвим для 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-запросы.