mysqli_quote_string(): безопасное экранирование строк в PHP 8.6
Проблема: SQL-инъекции в mysqli
В mysqli уже давно есть real_escape_string() для ручного экранирования строк. Но у неё есть известная уязвимость: при включённом режиме NO_BACKSLASH_ESCAPES и использовании двойных кавычек для обёртывания значения, экранирование не спасает от инъекции.
$mysqli->query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');
$value = '" OR 1=1 -- foo';
$sql = sprintf('SELECT id FROM foo WHERE name="%s"', $mysqli->real_escape_string($value));
$result = $mysqli->query($sql);
// Инъекция сработала! Запрос: SELECT id FROM foo WHERE name="" OR 1=1 -- foo"
Кроме того, real_escape_string() только экранирует спецсимволы, но не добавляет кавычки. Разработчик должен сам обернуть результат в '...' через sprintf или конкатенацию. Длинное имя функции + ручное добавление кавычек = слишком легко ошибиться.
Решение: mysqli_quote_string()
Новая функция mysqli_quote_string(), реализованная в PHP 8.6 (RFC от Kamil Tekiela), делает две вещи сразу:
- Экранирует строку так же, как
real_escape_string() - Оборачивает результат в одинарные кавычки
// ОО-стиль
$quoted = $mysqli->quote_string("Don't!");
// результат: "'Don\'t!'"
// Процедурный стиль
$quoted = mysqli_quote_string($mysqli, "Don't!");
Почему только одинарные кавычки
Двойные кавычки — нестандартный SQL. Стандарт SQL использует их для идентификаторов, а не для строковых литералов. MySQL поддерживает их как расширение, но именно это и создаёт уязвимость. Функция PDO::quote() в PDO тоже всегда использует одинарные кавычки — и это безопаснее. Новая функция в mysqli следует тому же принципу: нет выбора символа квотирования — нет уязвимости.
Безопасный пример
$mysqli->query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');
$value = '" OR 1=1 -- foo';
$sql = sprintf('SELECT id FROM foo WHERE name=%s', $mysqli->quote_string($value));
$result = $mysqli->query($sql);
// Безопасно! Запрос: SELECT id FROM foo WHERE name='" OR 1=1 -- foo'
// Инъекция не сработала.
Сигнатура
public function quote_string(string $string): string {}
function mysqli_quote_string(mysqli $mysql, string $string): string {}
Почему именно "quote_string", а не "quote"
В PDO функция называется quote(), но в mysqli важно подчеркнуть, что она работает только со строками. В не-strict режиме PHP можно передать другие типы, и в mysqli это распространённая ошибка новичков, подогретая онлайн-туториалами. Слово "string" в названии прямо указывает на тип параметра и помогает избежать ошибок.
Что дальше
Автор RFC надеется, что mysqli::real_escape_string() будет помечен как устаревший (deprecated) в одном из следующих минорных релизов. При миграции разработчикам нужно будет убрать вручную добавленные кавычки и заменить саму функцию. Важно не забыть заменить именно функцию — иначе инъекция останется.
Совместимость
Обратной несовместимости нет — просто добавляется новая функция. Если в вашем коде уже определена пользовательская функция mysqli_quote_string(), это вызовет конфликт.
Комментарии