TL; DR
mysql_real_escape_string()
не будет не обеспечивать никакой защиты (и, кроме того, может испортить ваши данные), если:
MySQL NO_BACKSLASH_ESCAPES
Режим SQL включен (что может быть , если только вы явно не выберете другой режим SQL каждый раз вы подключаете ); и
строковые литералы SQL заключаются в двойные кавычки "
символов.
Это было зарегистрировано как ошибка # 72458 и исправлено в MySQL v5.7.6 (см. Раздел « The Saving Grace », ниже).
Это еще один, (возможно, менее?) Неясный случай КРАЯ !!!
В знак уважения к превосходному ответу @ ircmaxell (действительно, это должно быть лестью, а не плагиатом!), Я приму его формат:
Атака
Начиная с демонстрации ...
mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); // could already be set
$var = mysql_real_escape_string('" OR 1=1 -- ');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
Это вернет все записи из таблицы test
. Расслоение:
Выбор режима SQL
mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');
Как указано в Строковые литералы :
Есть несколько способов включить символы кавычки в строку:
«'
» внутри строки, заключенной в «'
», можно записать как «''
».
«"
» внутри строки, заключенной в «"
», можно записать как «""
».
Перед символом кавычки должен стоять символ перехода («\
»).
«'
» внутри строки, заключенной в «"
», не требует специальной обработки и не нуждается в удвоении или экранировании. Таким же образом, «"
» внутри строки, заключенной в «'
», не требует специальной обработки.
Если режим SQL сервера включает NO_BACKSLASH_ESCAPES
, то третий из этих параметров & ndash; это обычный подход, принятый mysql_real_escape_string()
& ndash; недоступен: один из первых двух вариантов должен быть используется вместо Обратите внимание, что эффект четвертого маркера заключается в том, что нужно обязательно знать символ, который будет использоваться для кавычек литерала, чтобы избежать манипулирования данными.
Полезная нагрузка
" OR 1=1 --
Полезная нагрузка инициирует эту инъекцию буквально с символом "
. Нет конкретной кодировки. Никаких специальных символов. Никаких странных байтов.
mysql_real_escape_string ()
$var = mysql_real_escape_string('" OR 1=1 -- ');
К счастью, mysql_real_escape_string()
проверяет режим SQL и соответствующим образом корректирует его поведение. См libmysql.c
:
ulong STDCALL
mysql_real_escape_string(MYSQL *mysql, char *to,const char *from,
ulong length)
{
if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
return escape_quotes_for_mysql(mysql->charset, to, 0, from, length);
return escape_string_for_mysql(mysql->charset, to, 0, from, length);
}
Таким образом, другая основная функция, escape_quotes_for_mysql()
, вызывается, если используется режим NO_BACKSLASH_ESCAPES
SQL. Как упоминалось выше, такая функция должна знать, какой символ будет использоваться для кавычек литерала, чтобы повторять его, не вызывая повторения буквально другого символа кавычки.
Однако эта функция произвольно предполагает , что строка будет заключена в кавычки с использованием символа '
в одинарных кавычках. См charset.c
:
/*
Escape apostrophes by doubling them up
// [ deletia 839-845 ]
DESCRIPTION
This escapes the contents of a string by doubling up any apostrophes that
it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
effect on the server.
// [ deletia 852-858 ]
*/
size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info,
char *to, size_t to_length,
const char *from, size_t length)
{
// [ deletia 865-892 ]
if (*from == '\'')
{
if (to + 2 > to_end)
{
overflow= TRUE;
break;
}
*to++= '\'';
*to++= '\'';
}
Таким образом, двойные кавычки "
остаются без изменений (и удваиваются все одинарные кавычки '
символов) независимо от фактического символа, который используется для кавычек литерала ! В нашем случае $var
остается точно таким же, как аргумент, который был предоставлен для mysql_real_escape_string()
& mdash; как будто никакого побега не произошло вообще .
Запрос
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
Что-то формальности, обработанный запрос:
SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1
Как сказал мой ученый друг: поздравляю, вы только что успешно атаковали программу, используя mysql_real_escape_string()
...
Плохой
mysql_set_charset()
не может помочь, так как это не имеет ничего общего с наборами символов; mysqli::real_escape_string()
также не может, поскольку это просто другая оболочка для этой же функции.
Проблема, если она еще не очевидна,s * то, что вызов mysql_real_escape_string()
не может знать , с каким символом будет заключен литерал в кавычки, так как это оставлено на усмотрение разработчика позднее.Таким образом, в режиме NO_BACKSLASH_ESCAPES
буквально нет способа , чтобы эта функция могла безопасно экранировать каждый ввод для использования с произвольными кавычками (по крайней мере, не без удвоения символов, которые не требуют удвоения и, следовательно, манипулирования вашими данными).
Гадкий
Становится хуже.NO_BACKSLASH_ESCAPES
может быть не таким уж редким явлением из-за необходимости его использования для совместимости со стандартным SQL (например, см. Раздел 5.3 спецификации SQL-92 , а именно производство грамматики <quote symbol> ::= <quote><quote>
иотсутствие какого-либо особого значения, придаваемого обратной косой черте).Кроме того, его использование было явно рекомендовано в качестве обходного пути к (давно исправленному) багу , описанному в посте ircmaxell.Кто знает, некоторые администраторы БД могут даже настроить его на включение по умолчанию, чтобы не использовать неправильные методы экранирования, такие как addslashes()
.
Кроме того, режим SQLновое соединение устанавливается сервером в соответствии с его конфигурацией (которую пользователь SUPER
может изменить в любое время);таким образом, чтобы быть уверенным в поведении сервера, вы должны всегда явно указывать желаемый режим после подключения.
Экономия
Пока вы всегда явно задает режим SQL, который не включает NO_BACKSLASH_ESCAPES
, или заключает в кавычки строковые литералы MySQL, используя символ одинарных кавычек, эта ошибка не может привести к появлению уродливой головы: соответственно escape_quotes_for_mysql()
не будет использоваться, или ее предположение о том, какая цитатасимволы, требующие повторения, будут правильными.
По этой причине я рекомендую всем, кто использует NO_BACKSLASH_ESCAPES
, также включить режим ANSI_QUOTES
, так как это приведет к обычному использованию строковых литералов в одинарных кавычках,Обратите внимание, что это не предотвращает внедрение SQL в случае использования литералов в двойных кавычках - это просто уменьшает вероятность этого (поскольку нормальные, не злонамеренные запросы не будут работать).
В PDO,и его эквивалентная функция PDO::quote()
и его подготовленный эмулятор операторов вызывают mysql_handle_quoter()
- что делает именно это: он гарантирует, что экранированный литерал заключается в одинарные кавычки, поэтому выМожно быть уверенным, что PDO всегда защищен от этой ошибки.
Начиная с MySQL v5.7.6, эта ошибка была исправлена.См. журнал изменений :
Функциональность добавлена или изменена
Безопасные примеры
Взятые вместе с ошибкой, объясненной ircmaxell, следующие примеры полностью безопасны (при условии, что кто-либо используетMySQL более поздний, чем 4.1.20, 5.0.22, 5.1.11 или тот, который не использует кодировку соединения GBK / Big5):
mysql_set_charset($charset);
mysql_query("SET SQL_MODE=''");
$var = mysql_real_escape_string('" OR 1=1 /*');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
..., потому что мы явно выбрали режим SQLэто не включает NO_BACKSLASH_ESCAPES
.
mysql_set_charset($charset);
$var = mysql_real_escape_string("' OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
... потому что мы цитируем наш строковый литерал одинарными кавычками.
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(["' OR 1=1 /*"]);
... потому что подготовленные операторы PDO защищены от этой уязвимости (и также от ircmaxell, при условии, что вы используете PHP≥5.3.6 и набор символов был правильно задан в DSN; отключен).
$var = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");
... потому что функция PDO quote()
не только экранирует литерал, но и заключает его в кавычки (в одинарных кавычках '
символов); обратите внимание, что во избежание ошибки ircmaxell в этом случае вы должны использовать PHP≥5.3.6 и правильно установили набор символов в DSN.
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();
... потому что подготовленные MySQLi операторы безопасны.
Завершение
Таким образом, если вы:
- использовать нативно подготовленные операторы
OR
- использовать MySQL v5.7.6 или новее
OR
... тогда вы должны быть полностью в безопасности (уязвимости выходят за рамки возможности выхода строки в сторону).