Безопасная работа с базами данных mysqli в PHP: экранирование строк

Раздел: Программирование -> Безопасность

Основные подходы к экранированию строк в mysqli

При взаимодействии с базой данных через расширение mysqli в PHP важно корректно обрабатывать пользовательские данные, чтобы предотвратить SQL инъекции. Наиболее надёжным решением считается использование подготовленных запросов (prepared statements), однако существуют и другие методы, каждый из которых имеет свои сценарии применения и ограничения.

Подготовленные запросы (prepared statements) с параметрами

Как гарантированно избежать SQL инъекций при любых входных данных?

Основной и наиболее эффективный способ - применение prepared statements через методы mysqli_prepare() и mysqli_stmt_bind_param(). Данные передаются отдельно от SQL шаблона, поэтому они никогда не интерпретируются как часть запроса.


$mysqli = new mysqli('localhost', 'user', 'pass', 'db');
$stmt = $mysqli->prepare('SELECT id, name FROM users WHERE email = ?');
$stmt->bind_param('s', $email);
$email = $_POST['email'];
$stmt->execute();
$result = $stmt->get_result();

Access php (доступ к файлам в php)

Тип 's' указывает на строковый параметр; можно использовать 'i' для целых, 'd' для дробных, 'b' для BLOB. Система автоматически экранирует все символы, опасные для синтаксиса SQL.

Типичные проблемы и их решение:

  • Проблема: Ошибка "Call to a member function bind_param() on boolean" - запрос не скомпилирован из-за синтаксической ошибки в SQL. Решение: проверять результат prepare: if (!$stmt) echo $mysqli->error;
  • Проблема: Забыли вызвать bind_param() перед execute. Решение: всегда связывать параметры сразу после prepare.
  • Проблема: Передача слишком большого количества параметров или несоответствие типов. Решение: строго следить за количеством и порядком знаков '?' и соответствующих переменных.

Цели и случаи использования: подготовленные запросы подходят для любых запросов (INSERT, UPDATE, SELECT, DELETE) с динамическими данными. Они обеспечивают максимальную защиту и рекомендуются как основной инструмент.

Функция mysqli_real_escape_string()

Как экранировать строку вручную для подстановки в SQL?

Эта функция экранирует специальные символы с учётом текущей кодировки соединения. Применяется только для строковых значений, вставляемых непосредственно в запрос (не рекомендуется, если доступны prepared statements).


$name = mysqli_real_escape_string($mysqli, $_POST['name']);
$sql = "INSERT INTO users (name) VALUES ('$name')";
$mysqli->query($sql);

Php filter (фильтрация данных в php)

Важно:

  • Необходимо установить правильную кодировку соединения (например, $mysqli->set_charset('utf8')), иначе экранирование может работать неверно.
  • Функция бесполезна для числовых полей - числа не нужно обрамлять кавычками, и экранирование не предотвращает инъекцию, если переменная не приведена к числу.

Типичные ошибки:

  • Ошибка: Использование функции без активного соединения (до вызова new mysqli()). Решение: вызывать только после установки соединения.
  • Ошибка: Злоупотребление экранированием в запросах с LIKE - символы '%' и '_' не экранируются, что может привести к неожиданным результатам. Решение: дополнительно экранировать эти символы вручную: $name = addcslashes($name, '%_');
  • Ошибка: Двойное экранирование (например, после использования addslashes()). Решение: использовать только один механизм.

Случаи использования: когда невозможно применить prepared statements (например, при динамическом построении части запроса, такой как имя таблицы или столбца - эти части не подставляются через параметры).

Ручное экранирование с помощью addslashes() - не рекомендуется

Как быстро экранировать строку без учёта кодировки?

addslashes() добавляет обратный слеш перед одинарными кавычками, двойными кавычками, обратным слешем и null-байтом. Однако этот метод не учитывает кодировку символов, что может привести к проблемам с многобайтовыми последовательностями (например, в UTF-8).


$name = addslashes($_POST['name']);
$sql = "SELECT * FROM users WHERE name = '$name'";

Php пароль (работа с паролями в php)

Проблемы:

  • Если в строке присутствует символ с кодом 0x80 или 0x81 (в CP1251), за которым идёт обратный слеш, может возникнуть инъекция.
  • Не защищает от атак на основе кодировок (GBK и подобных).

Когда применяется: практически никогда, лучше использовать mysqli_real_escape_string() или prepared statements.

Метод quote() из PDO (альтернативный подход)

Как экранировать строку в стиле PDO, работая с mysqli?

В самом mysqli нет встроенного метода quote(), но можно написать обёртку, которая добавляет кавычки и экранирует. Однако это не меняет сути - всё равно используется real_escape_string. Поэтому предпочтительнее сразу перейти на PDO или использовать подготовленные запросы.


function quote($value) {
    return "'" . mysqli_real_escape_string($GLOBALS['mysqli'], $value) . "'";
}
$name = quote($_POST['name']);
$sql = "INSERT INTO users (name) VALUES ($name)";

Этот подход повторяет недостатки real_escape_string и не рекомендуется для новых проектов.

- права php (управление правами доступа в php)
- Php пароль mysql (пароль для mysql в php)
- Domain block php (блокировка домена в php)

Расширенные примеры экранирования в mysqli

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

Пример

$mysqli = new mysqli('localhost', 'root', '', 'test');
$mysqli->set_charset('utf8mb4');

$stmt = $mysqli->prepare('INSERT INTO products (name, price, quantity) VALUES (?, ?, ?)');
$stmt->bind_param('sdi', $name, $price, $qty);

$name = 'Ноутбук "Acer"';
$price = 54999.99;
$qty = 10;
$stmt->execute();

echo "Запись добавлена, ID: " . $stmt->insert_id;
Запись добавлена, ID: 1

Пояснение: В строке 'sdi' указаны типы: строка (s), double (d), integer (i). Все три переменные передаются по ссылке, но их значения подставляются безопасно. Цена с десятичной точкой обрабатывается корректно.

Пример 2. Экранирование через mysqli_real_escape_string с LIKE-запросом

Пример

$mysqli = new mysqli('localhost', 'root', '', 'test');
$mysqli->set_charset('utf8');

$search = $_POST['search']; // допустим пользователь ввёл "%test_"
$search_esc = mysqli_real_escape_string($mysqli, $search);
// дополнительные экранирование служебных символов LIKE
$search_esc = addcslashes($search_esc, '%_');

$sql = "SELECT * FROM articles WHERE title LIKE '%$search_esc%'";
$result = $mysqli->query($sql);

echo "Найдено записей: " . $result->num_rows;
Найдено записей: 3

Пояснение: Без addcslashes пользователь мог бы ввести % или _, что изменило бы логику LIKE. Функция mysqli_real_escape_string не экранирует эти символы, поэтому требуется ручная обработка.

Пример 3. Проблема с кодировкой при использовании addslashes

Пример

$mysqli = new mysqli('localhost', 'root', '', 'test');
// Кодировка не установлена

$input = "\xbf\x27 OR 1=1 -- "; // потенциально опасная строка в GBK
$escaped = addslashes($input);
echo $escaped; // выведет \xbf\x5c\x27...
// В некоторых кодировках последовательность \xbf\x5c может быть воспринята как один многобайтовый символ
// что делает экранирование неэффективным
\xbf\x5c' OR 1=1 --

Пояснение: Если кодировка соединения не совпадает с кодировкой данных (например, GBK), обратный слеш может быть "съеден" многобайтовой последовательностью. Это классический вектор атаки. Решение - всегда устанавливать кодировку через set_charset() и использовать mysqli_real_escape_string.

Пример 4. Динамическое имя таблицы - когда prepared statements не помогают

Пример

$mysqli = new mysqli('localhost', 'root', '', 'test');
$table = $_GET['table']; // небезопасно, но иногда необходимо
// Валидация: разрешённые таблицы
$allowed = ['users', 'orders', 'products'];
if (!in_array($table, $allowed)) {
    die('Недопустимое имя таблицы');
}

$sql = "SELECT * FROM `$table` WHERE id = ?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('i', $id);
$id = $_GET['id'];
$stmt->execute();
$result = $stmt->get_result();
(вывод результата SELECT из таблицы users)

Пояснение: Имя таблицы нельзя передать через параметр в prepared statement, поэтому его приходится вставлять напрямую. Единственный способ обезопаситься - белый список допустимых значений. Экранирование через real_escape_string тут не поможет, так как имена таблиц обрамляются обратными кавычками, а экранирование для них не предусмотрено.

Экранирование строк в mysqli в PHP - comments

En
Mysqli escape string php (php)