Есть ли возможность SQL-инъекции даже при использовании mysql_real_escape_string()
функции?
Рассмотрим этот пример ситуации. SQL построен на PHP следующим образом:
$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));
$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";
Я слышал, как многие люди говорят мне, что подобный код все еще опасен и его можно взломать даже с mysql_real_escape_string()
использованием функции. Но я не могу думать ни о каком возможном подвиге?
Классические инъекции, как это:
aaa' OR 1=1 --
не работает.
Знаете ли вы о возможных инъекциях, которые могли бы пройти через код PHP выше?
mysql_*
функции в новом коде . Они больше не поддерживаются, и процесс амортизации уже начался. Видишь красную коробку ? Вместо этогоузнайте о готовых утверждениях и используйте PDO или MySQLi - эта статья поможет вам решить, какие именно. Если вы выбираете PDO, вот хороший урок .mysql_*
функции уже выдаютE_DEPRECATED
предупреждение.ext/mysql
Расширение не поддерживается в течение более 10 лет. Вы действительно так бредите?Ответы:
Рассмотрим следующий запрос:
mysql_real_escape_string()
не защитит вас от этого. Тот факт, что вы используете одинарные кавычки (' '
) вокруг переменных внутри запроса, защищает вас от этого. Следующее также вариант:источник
mysql_query()
что не выполняет несколько операторов, нет?DROP TABLE
практике злоумышленник с большей вероятностьюSELECT passwd FROM users
. В последнем случае второй запрос обычно выполняется с использованиемUNION
предложения.(int)mysql_real_escape_string
- это не имеет никакого смысла. Это не отличается от(int)
всех. И они будут выдавать один и тот же результат для каждого вводаmysql_real_escape_string
, а неmysql_real_escape_integer
. Это не значит использовать с целочисленными полями.Короткий ответ: да, да, есть способ обойти
mysql_real_escape_string()
.Для очень скрытых краев случаев!
Длинный ответ не так прост. Он основан на атаке, продемонстрированной здесь .
Атака
Итак, начнем с показа атаки ...
При определенных обстоятельствах это вернет более 1 строки. Давайте рассмотрим, что здесь происходит:
Выбор набора символов
Для этой атаки на работу, нам нужна кодировка , что сервер ожидают на связи как для кодирования ,
'
как в ASCII , т.е.0x27
и иметь некоторый символ , чьи окончательный байт является ASCII\
т.е.0x5c
. Как выясняется, есть 5 таких кодировок , поддерживаемых в MySQL 5.6 по умолчанию:big5
,cp932
,gb2312
,gbk
иsjis
. Мы выберемgbk
здесь.Теперь очень важно отметить использование
SET NAMES
здесь. Это устанавливает набор символов на сервере . Если бы мы использовали вызов функции C APImysql_set_charset()
, у нас все было бы в порядке (в версиях MySQL с 2006 года). Но подробнее о том, почему через минуту ...Полезная нагрузка
Полезная нагрузка, которую мы собираемся использовать для этой инъекции, начинается с последовательности байтов
0xbf27
. Во-gbk
первых, это недопустимый многобайтовый символ; вlatin1
это строка¿'
. Обратите внимание, что вlatin1
иgbk
,0x27
само по себе является буквальным'
символом.Мы выбрали эту полезную нагрузку, потому что, если бы мы ее вызвали
addslashes()
, мы вставили бы ASCII,\
т.е.0x5c
перед'
символом. Таким образом, мы получим0xbf5c27
, чтоgbk
представляет собой последовательность из двух символов: с0xbf5c
последующим0x27
. Или, другими словами, действительный символ, за которым следует неоткрытый'
. Но мы не используемaddslashes()
. Итак, к следующему шагу ...mysql_real_escape_string ()
Вызов C API
mysql_real_escape_string()
отличается от того,addslashes()
что он знает набор символов соединения. Таким образом, он может выполнить экранирование правильно для набора символов, который ожидает сервер. Однако до этого момента клиент думал, что мы все еще используемlatin1
для соединения, потому что мы никогда не говорили об этом иначе. Мы сказали серверу, который используемgbk
, но клиент все еще думает, что это такlatin1
.Следовательно, вызов
mysql_real_escape_string()
вставляет обратную косую черту, и у нас есть свободный'
символ зависания в нашем «экранированном» контенте! В самом деле, если бы мы должны были смотреть на$var
вgbk
наборе символов, мы видим:Что именно то, что требуется для атаки.
Запрос
Эта часть просто формальность, но вот обработанный запрос:
Поздравляем, вы только что успешно атаковали программу, используя
mysql_real_escape_string()
...Плохо
Становится хуже.
PDO
по умолчанию эмулирует подготовленные операторы с MySQL. Это означает, что на стороне клиента он в основном выполняет sprintfmysql_real_escape_string()
(в библиотеке C), что означает, что следующее приведет к успешному внедрению:Теперь стоит отметить, что вы можете предотвратить это, отключив эмулированные подготовленные операторы:
Это обычно приводит к истинно подготовленному утверждению (т. Е. Данные отправляются в отдельном пакете от запроса). Тем не менее, следует помнить , что PDO будет молча запасным вариантом , чтобы эмулировать заявления , что MySQL не может подготовить изначально: те , которые можно будут перечислены в руководстве, но будьте осторожны , чтобы выбрать подходящую версию сервера).
Гадкий
Я сказал в самом начале, что мы могли бы предотвратить все это, если бы использовали
mysql_set_charset('gbk')
вместоSET NAMES gbk
. И это правда, если вы используете версию MySQL с 2006 года.Если вы используете более раннюю версию MySQL, затем ошибку в
mysql_real_escape_string()
виде , что недопустимые символы многобайтовых , такие как в наших полезных нагрузках рассматривались как отдельные байты для побега целей , даже если клиент был правильно информирован о кодировании соединения и поэтому эта атака будет все еще удастся Ошибка была исправлена в MySQL 4.1.20 , 5.0.22 и 5.1.11 .Но хуже всего то,
PDO
что не раскрывал C APImysql_set_charset()
до 5.3.6, поэтому в предыдущих версиях он не мог предотвратить эту атаку для всех возможных команд! Это теперь выставлено как параметр DSN .Спасительная Грация
Как мы уже говорили, чтобы атака работала, соединение с базой данных должно быть закодировано с использованием уязвимого набора символов.
utf8mb4
это не уязвима , и все же может поддерживать каждый символ Unicode: чтобы вы могли выбрать для использования , что вместо, но он был доступен только начиная с MySQL 5.5.3. Альтернативаutf8
, которая также не уязвима и может поддерживать всю базовую многоязычную плоскость Unicode .Кроме того, вы можете включить
NO_BACKSLASH_ESCAPES
режим SQL, который (среди прочего) изменяет работуmysql_real_escape_string()
. Если этот режим включен,0x27
он будет заменен на,0x2727
а не,0x5c27
и, таким образом, процесс выхода не может создавать допустимые символы в любой из уязвимых кодировок, где они ранее не существовали (то0xbf27
есть все еще и0xbf27
т. Д.), Поэтому сервер все равно отклонит строку как недопустимую. , Однако см . Ответ @ eggyal о другой уязвимости, которая может возникнуть при использовании этого режима SQL.Безопасные Примеры
Следующие примеры безопасны:
Потому что сервер ожидает
utf8
...Потому что мы правильно установили набор символов, чтобы клиент и сервер совпадали.
Потому что мы отключили эмулированные подготовленные заявления.
Потому что мы установили правильный набор символов.
Потому что MySQLi постоянно делает действительно подготовленные операторы.
Завершение
Если ты:
mysql_set_charset()
/$mysqli->set_charset()
/ параметр набора символов DSN для PDO (в PHP ≥ 5.3.6)ИЛИ
utf8
/latin1
/ascii
/ etc)Вы на 100% в безопасности.
В противном случае вы уязвимы, даже если вы используете
mysql_real_escape_string()
...источник
addslashes
. Я основал эту уязвимость на этом. Попробуй сам. Получите MySQL 5.0, запустите этот эксплойт и убедитесь сами. Что касается того, как поместить это в PUT / GET / POST, это TRIVIAL. Входные данные - это просто байтовые потоки.char(0xBF)
это просто читаемый способ генерации байта. Я продемонстрировал эту уязвимость в прямом эфире перед несколькими конференциями. Поверь мне в этом ... Но если нет, попробуй сам. Это работает ...?var=%BF%27+OR+1=1+%2F%2A
в URL,$var = $_GET['var'];
в коде, и Боб - твой дядя.Это еще один, (возможно, менее?) Неясный случай с краем !!!
В знак уважения к отличному ответу @ ircmaxell (на самом деле это должна быть лесть, а не плагиат!), Я приму его формат:
Атака
Начиная с демонстрации ...
Это вернет все записи из
test
таблицы. Расслоение:Выбор режима SQL
Как описано в строковых литералах :
Если режим SQL сервера включает
NO_BACKSLASH_ESCAPES
, то третий из этих параметров, который является обычным подходом, принятым для этогоmysql_real_escape_string()
, недоступен: вместо него должен использоваться один из первых двух параметров. Обратите внимание, что эффект четвертого маркера заключается в том, что необходимо обязательно знать символ, который будет использоваться для цитирования литерала, чтобы избежать манипулирования данными.Полезная нагрузка
Полезная нагрузка инициирует эту инъекцию буквально с
"
персонажем. Нет конкретной кодировки. Никаких специальных символов. Никаких странных байтов.mysql_real_escape_string ()
К счастью,
mysql_real_escape_string()
проверяет режим SQL и корректирует его поведение соответствующим образом. Смотритеlibmysql.c
:Таким образом, другая основная функция
escape_quotes_for_mysql()
вызывается, если используетсяNO_BACKSLASH_ESCAPES
режим SQL. Как упомянуто выше, такая функция должна знать, какой символ будет использоваться для кавычек литерала, чтобы повторять его, не вызывая повторения буквально другого символа кавычки.Однако эта функция произвольно предполагает, что строка будет заключена в кавычки с использованием
'
символа одинарных кавычек . Смотритеcharset.c
:Таким образом, он оставляет
"
символы двойной кавычки нетронутыми (и удваивает все'
символы одинарных кавычек ) независимо от фактического символа, который используется для цитирования литерала ! В нашем случае$var
остается точно такой же аргумент, который был предоставленmysql_real_escape_string()
- как будто никакого побега не произошло вообще .Запрос
Нечто формальное, обработанный запрос:
Как сказал мой ученый друг: поздравляю, вы только что успешно атаковали программу, используя
mysql_real_escape_string()
...Плохо
mysql_set_charset()
не может помочь, так как это не имеет ничего общего с наборами символов; и не можетmysqli::real_escape_string()
, так как это просто другая оболочка для этой же функции.Проблема, если она еще не очевидна, заключается в том, что при вызове
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) :
... потому что мы явно выбрали режим SQL, который не включает
NO_BACKSLASH_ESCAPES
.... потому что мы цитируем наш строковый литерал одинарными кавычками.
... потому что подготовленные операторы PDO защищены от этой уязвимости (и ircmaxell также, при условии, что вы используете PHP≥5.3.6 и набор символов был правильно задан в DSN; или эмуляция подготовленных операторов была отключена) ,
... потому что
quote()
функция PDO не только экранирует литерал, но и заключает его в кавычки (в одинарных кавычках'
); обратите внимание, что во избежание ошибки ircmaxell в этом случае вы должны использовать PHP≥5.3.6 и правильно установить набор символов в DSN.... потому что подготовленные MySQLi заявления безопасны.
Завершение
Таким образом, если вы:
ИЛИ
ИЛИ
в дополнение к использованию одного из решений в резюме ircmaxell, используйте по крайней мере одно из:
NO_BACKSLASH_ESCAPES
... тогда вы должны быть полностью в безопасности (уязвимости выходят за рамки выхода строки).
источник
"
для строк в первую очередь. SQL говорит, что это для идентификаторов. Но да ... просто еще один пример того, как MySQL говорит: "Винтовые стандарты, я буду делать все, что захочу". (К счастью, вы можете включитьANSI_QUOTES
в режим исправление ошибок цитирования. Однако открытое игнорирование стандартов является более серьезной проблемой, которая может потребовать более жестких мер.)quote()
функции PDO, но подготовленные операторы являются гораздо более безопасным и более подходящим способом избежать инъекций в целом. Конечно, если вы непосредственно связали неэкранированные переменные в свой SQL, то вы наверняка уязвимы для внедрения независимо от того, какие методы вы будете использовать после этого.Ну, на самом деле ничто не может пройти через это, кроме
%
подстановочных знаков. Это может быть опасно, если вы используетеLIKE
оператор, поскольку злоумышленник может ввести просто%
логин, если вы его не отфильтруете, и вам придется просто взломать пароль любого из ваших пользователей. Люди часто предлагают использовать подготовленные операторы, чтобы сделать их на 100% безопасными, так как данные не могут таким образом вмешиваться в сам запрос. Но для таких простых запросов, вероятно, было бы более эффективно сделать что-то вроде$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);
источник
more efficient
чем использование подготовленных заявлений? (Подготовленные операторы всегда работают, библиотека может быть быстро исправлена в случае атак, не выявляет человеческих ошибок [таких, как неправильный ввод полной строки замены), и имеет значительные преимущества в производительности, если оператор используется повторно.)