SQL-инъекция, которая обходит mysql_real_escape_string ()

644

Есть ли возможность 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 выше?

Ричард Кноп
источник
34
@ThiefMaster - я предпочитаю не давать подробных ошибок, таких как неверный пользователь / неверный пароль ... он говорит торговцам грубой силы, что у них есть действительный идентификатор пользователя, и это просто пароль, который им нужно угадать
Марк Бейкер
18
Это ужасно с точки зрения удобства использования. Иногда вы не могли использовать свой основной ник / имя пользователя / адрес электронной почты и забыть об этом через некоторое время, или сайт удалил вашу учетную запись из-за неактивности. Тогда это очень раздражает, если вы продолжаете пробовать пароли и, возможно, даже блокируете свой IP-адрес, даже если ваше имя пользователя недействительно.
ThiefMaster
50
Пожалуйста, не используйте mysql_*функции в новом коде . Они больше не поддерживаются, и процесс амортизации уже начался. Видишь красную коробку ? Вместо этогоузнайте о готовых утверждениях и используйте PDO или MySQLi - эта статья поможет вам решить, какие именно. Если вы выбираете PDO, вот хороший урок .
tereško
13
@machineaddict, начиная с версии 5.5 (которая была выпущена недавно), mysql_*функции уже выдают E_DEPRECATEDпредупреждение. ext/mysqlРасширение не поддерживается в течение более 10 лет. Вы действительно так бредите?
Терешко
13
@machineaddict Они только что удалили это расширение в PHP 7.0, но это еще не 2050 год.
GGG

Ответы:

379

Рассмотрим следующий запрос:

$iId = mysql_real_escape_string("1 OR 1=1");    
$sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string()не защитит вас от этого. Тот факт, что вы используете одинарные кавычки ( ' ') вокруг переменных внутри запроса, защищает вас от этого. Следующее также вариант:

$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";
Уэсли ван Опдорп
источник
9
Но это не будет настоящей проблемой, потому mysql_query()что не выполняет несколько операторов, нет?
Пекка
11
@Pekka, хотя обычный пример таков, на DROP TABLEпрактике злоумышленник с большей вероятностью SELECT passwd FROM users. В последнем случае второй запрос обычно выполняется с использованием UNIONпредложения.
Жако
58
(int)mysql_real_escape_string- это не имеет никакого смысла. Это не отличается от (int)всех. И они будут выдавать один и тот же результат для каждого ввода
zerkms
28
Это скорее неправильное использование функции, чем все остальное. В конце концов, это называется mysql_real_escape_string, а не mysql_real_escape_integer. Это не значит использовать с целочисленными полями.
NullUserException
11
@ircmaxell, но ответ полностью вводит в заблуждение. Очевидно, вопрос заключается в содержании цитат. «Кавычек нет» не является ответом на этот вопрос.
Pacerier
629

Короткий ответ: да, да, есть способ обойтиmysql_real_escape_string() .

Для очень скрытых краев случаев!

Длинный ответ не так прост. Он основан на атаке, продемонстрированной здесь .

Атака

Итак, начнем с показа атаки ...

mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

При определенных обстоятельствах это вернет более 1 строки. Давайте рассмотрим, что здесь происходит:

  1. Выбор набора символов

    mysql_query('SET NAMES gbk');

    Для этой атаки на работу, нам нужна кодировка , что сервер ожидают на связи как для кодирования , 'как в ASCII , т.е. 0x27 и иметь некоторый символ , чьи окончательный байт является ASCII \т.е. 0x5c. Как выясняется, есть 5 таких кодировок , поддерживаемых в MySQL 5.6 по умолчанию: big5, cp932, gb2312, gbkи sjis. Мы выберем gbkздесь.

    Теперь очень важно отметить использование SET NAMESздесь. Это устанавливает набор символов на сервере . Если бы мы использовали вызов функции C API mysql_set_charset(), у нас все было бы в порядке (в версиях MySQL с 2006 года). Но подробнее о том, почему через минуту ...

  2. Полезная нагрузка

    Полезная нагрузка, которую мы собираемся использовать для этой инъекции, начинается с последовательности байтов 0xbf27. Во- gbkпервых, это недопустимый многобайтовый символ; в latin1это строка ¿'. Обратите внимание, что в latin1 и gbk , 0x27само по себе является буквальным 'символом.

    Мы выбрали эту полезную нагрузку, потому что, если бы мы ее вызвали addslashes(), мы вставили бы ASCII, \т.е. 0x5cперед 'символом. Таким образом, мы получим 0xbf5c27, что gbkпредставляет собой последовательность из двух символов: с 0xbf5cпоследующим 0x27. Или, другими словами, действительный символ, за которым следует неоткрытый '. Но мы не используем addslashes(). Итак, к следующему шагу ...

  3. mysql_real_escape_string ()

    Вызов C API mysql_real_escape_string()отличается от того, addslashes()что он знает набор символов соединения. Таким образом, он может выполнить экранирование правильно для набора символов, который ожидает сервер. Однако до этого момента клиент думал, что мы все еще используем latin1для соединения, потому что мы никогда не говорили об этом иначе. Мы сказали серверу, который используем gbk, но клиент все еще думает, что это так latin1.

    Следовательно, вызов mysql_real_escape_string()вставляет обратную косую черту, и у нас есть свободный 'символ зависания в нашем «экранированном» контенте! В самом деле, если бы мы должны были смотреть на $varв gbkнаборе символов, мы видим:

    OR 'ИЛИ 1 = 1 / *

    Что именно то, что требуется для атаки.

  4. Запрос

    Эта часть просто формальность, но вот обработанный запрос:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1

Поздравляем, вы только что успешно атаковали программу, используя mysql_real_escape_string()...

Плохо

Становится хуже. PDOпо умолчанию эмулирует подготовленные операторы с MySQL. Это означает, что на стороне клиента он в основном выполняет sprintf mysql_real_escape_string()(в библиотеке C), что означает, что следующее приведет к успешному внедрению:

$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Теперь стоит отметить, что вы можете предотвратить это, отключив эмулированные подготовленные операторы:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Это обычно приводит к истинно подготовленному утверждению (т. Е. Данные отправляются в отдельном пакете от запроса). Тем не менее, следует помнить , что 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 API mysql_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.

Безопасные Примеры

Следующие примеры безопасны:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Потому что сервер ожидает utf8...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Потому что мы правильно установили набор символов, чтобы клиент и сервер совпадали.

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Потому что мы отключили эмулированные подготовленные заявления.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Потому что мы установили правильный набор символов.

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

Потому что MySQLi постоянно делает действительно подготовленные операторы.

Завершение

Если ты:

  • Использовать современные версии MySQL (последняя версия 5.1, все версии 5.5, 5.6 и т. Д.) И mysql_set_charset() / $mysqli->set_charset()/ параметр набора символов DSN для PDO (в PHP ≥ 5.3.6)

ИЛИ

  • Не используйте уязвимый набор символов для кодирования соединения (вы используете только utf8/ latin1/ ascii/ etc)

Вы на 100% в безопасности.

В противном случае вы уязвимы, даже если вы используетеmysql_real_escape_string() ...

ircmaxell
источник
3
PDO, эмулирующий подготовку операторов для MySQL, правда? Я не вижу никакой причины, по которой это будет сделано, поскольку драйвер поддерживает его изначально. Нет?
Netcoder
16
Оно делает. Они говорят, что в документации это не так. Но в исходном коде это ясно видно и легко исправить. Я записываю это на счет некомпетентности разработчиков.
Теодор Р. Смит
5
@ TheodoreR.Smith: Это не так легко исправить. Я работал над изменением настроек по умолчанию, но при переключении он не проходит тестовую нагрузку. Так что это большее изменение, чем кажется. Я все еще надеюсь закончить это к 5,5 ...
ircmaxell
14
@shadyyx: Нет, уязвимость, о которой говорилось в статье addslashes. Я основал эту уязвимость на этом. Попробуй сам. Получите MySQL 5.0, запустите этот эксплойт и убедитесь сами. Что касается того, как поместить это в PUT / GET / POST, это TRIVIAL. Входные данные - это просто байтовые потоки. char(0xBF)это просто читаемый способ генерации байта. Я продемонстрировал эту уязвимость в прямом эфире перед несколькими конференциями. Поверь мне в этом ... Но если нет, попробуй сам. Это работает ...
ircmaxell
5
@shadyyx: Что касается передачи такой забавы в $ _GET ... ?var=%BF%27+OR+1=1+%2F%2Aв URL, $var = $_GET['var'];в коде, и Боб - твой дядя.
Цао
183

TL; DR

mysql_real_escape_string()не обеспечит никакой защиты (и, кроме того, может испортить ваши данные), если:

  • NO_BACKSLASH_ESCAPESРежим SQL MySQL включен (что может быть, если вы не выберете другой режим SQL при каждом подключении ); а также

  • строковые литералы SQL заключаются в двойные кавычки ".

Это было зарегистрировано как ошибка # 72458 и исправлено в MySQL v5.7.6 (см. Раздел « Экономия » ниже).

Это еще один, (возможно, менее?) Неясный случай с краем !!!

В знак уважения к отличному ответу @ 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таблицы. Расслоение:

  1. Выбор режима SQL

    mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');

    Как описано в строковых литералах :

    Есть несколько способов включить символы кавычки в строку:

    • « '» Внутри строки, заключенной в « '», может быть написано как « ''».

    • « "» Внутри строки, заключенной в « "», может быть написано как « ""».

    • Перед символом кавычки должен стоять символ перехода (« \»).

    • « '» Внутри строки, заключенной в « "», не требует особой обработки и не нуждается в удвоении или экранировании. Точно так же « "» внутри строки, заключенной в « '», не нуждается в особой обработке.

    Если режим SQL сервера включает NO_BACKSLASH_ESCAPES, то третий из этих параметров, который является обычным подходом, принятым для этого mysql_real_escape_string(), недоступен: вместо него должен использоваться один из первых двух параметров. Обратите внимание, что эффект четвертого маркера заключается в том, что необходимо обязательно знать символ, который будет использоваться для цитирования литерала, чтобы избежать манипулирования данными.

  2. Полезная нагрузка

    " OR 1=1 -- 

    Полезная нагрузка инициирует эту инъекцию буквально с "персонажем. Нет конкретной кодировки. Никаких специальных символов. Никаких странных байтов.

  3. 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()- как будто никакого побега не произошло вообще .

  4. Запрос

    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(), так как это просто другая оболочка для этой же функции.

Проблема, если она еще не очевидна, заключается в том, что при вызове 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 эта ошибка была исправлена. Смотрите журнал изменений :

Функциональность добавлена ​​или изменена

  • Несовместимое изменение: новая функция C API,mysql_real_escape_string_quote()была реализована в качестве замены,mysql_real_escape_string()потому что последняя функция может не в состоянии правильно кодировать символы, когдаNO_BACKSLASH_ESCAPESвключен режим SQL. В этом случаеmysql_real_escape_string()нельзя экранировать символы кавычек, кроме как путем удвоения их, и чтобы сделать это правильно, он должен знать больше информации о контексте цитирования, чем доступно. mysql_real_escape_string_quote()принимает дополнительный аргумент для указания контекста цитирования. Подробнее об использовании см. Mysql_real_escape_string_quote () .

     Запись

    Приложения должны быть изменены, чтобы использовать mysql_real_escape_string_quote()вместо того mysql_real_escape_string(), что теперь происходит сбой и выдает CR_INSECURE_API_ERRошибку, если NO_BACKSLASH_ESCAPESвключено.

    Ссылки: См. Также Ошибка № 19211994.

Безопасные Примеры

Взятые вместе с ошибкой, объясненной 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");

... потому что quote()функция PDO не только экранирует литерал, но и заключает его в кавычки (в одинарных кавычках '); обратите внимание, что во избежание ошибки 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 заявления безопасны.

Завершение

Таким образом, если вы:

  • использовать нативно подготовленные высказывания

ИЛИ

  • использовать MySQL v5.7.6 или новее

ИЛИ

  • в дополнение к использованию одного из решений в резюме ircmaxell, используйте по крайней мере одно из:

    • PDO;
    • строковые литералы в одинарных кавычках; или
    • явно установленный режим SQL, который не включает NO_BACKSLASH_ESCAPES

... тогда вы должны быть полностью в безопасности (уязвимости выходят за рамки выхода строки).

eggyal
источник
10
Таким образом, TL; DR был бы похож на «существует серверный режим NO_BACKSLASH_ESCAPES mysql, который может вызвать инъекцию, если вы не используете одинарные кавычки.
Ваш здравый смысл
Я не могу получить доступ к bugs.mysql.com/bug.php?id=72458 ; Я просто получаю страницу с отказом в доступе. Это скрыто от общественности из-за проблемы безопасности? Кроме того, правильно ли я понимаю из этого ответа, что вы открываете уязвимость? Если так, поздравляю.
Марк Эмери
1
Люди не должны использовать "для строк в первую очередь. SQL говорит, что это для идентификаторов. Но да ... просто еще один пример того, как MySQL говорит: "Винтовые стандарты, я буду делать все, что захочу". (К счастью, вы можете включить ANSI_QUOTESв режим исправление ошибок цитирования. Однако открытое игнорирование стандартов является более серьезной проблемой, которая может потребовать более жестких мер.)
cHao
2
@DanAllen: мой ответ был немного шире, поскольку вы можете избежать этой конкретной ошибки с помощью quote()функции PDO, но подготовленные операторы являются гораздо более безопасным и более подходящим способом избежать инъекций в целом. Конечно, если вы непосредственно связали неэкранированные переменные в свой SQL, то вы наверняка уязвимы для внедрения независимо от того, какие методы вы будете использовать после этого.
eggyal
1
@eggyall: наша система основана на втором безопасном примере выше. Есть ошибки, где mysql_real_escape_string был опущен. Исправление их в аварийном режиме, кажется, является разумным путем, надеясь, что мы не получим ядерное оружие до исправлений. Мое обоснование заключается в том, что преобразование в подготовленные заявления будет гораздо более длительным процессом, который должен последовать после. Является ли причина, по которой подготовленные заявления безопаснее, из-за того, что ошибки не создают уязвимостей? Другими словами, правильно ли реализован 2-й пример, описанный выше, так же безопасно, как подготовленные операторы?
DanAllen
18

Ну, на самом деле ничто не может пройти через это, кроме %подстановочных знаков. Это может быть опасно, если вы используете LIKEоператор, поскольку злоумышленник может ввести просто %логин, если вы его не отфильтруете, и вам придется просто взломать пароль любого из ваших пользователей. Люди часто предлагают использовать подготовленные операторы, чтобы сделать их на 100% безопасными, так как данные не могут таким образом вмешиваться в сам запрос. Но для таких простых запросов, вероятно, было бы более эффективно сделать что-то вроде$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);

Slava
источник
2
+1, но подстановочные знаки для предложения LIKE, а не простого равенства.
Дор
7
Какой мерой вы считаете простую замену, more efficientчем использование подготовленных заявлений? (Подготовленные операторы всегда работают, библиотека может быть быстро исправлена ​​в случае атак, не выявляет человеческих ошибок [таких, как неправильный ввод полной строки замены), и имеет значительные преимущества в производительности, если оператор используется повторно.)
MatBailie
7
@Slava: вы фактически ограничиваете имена пользователей и пароли только символами. Большинство людей, которые знают что-либо о безопасности, сочли бы это плохой идеей, поскольку она значительно сокращает пространство поиска. Конечно, они также сочли бы плохой идеей хранить пароли в виде открытого текста в базе данных, но нам не нужно усугублять проблему. :)
cHao
2
@ cHao, мое предложение касается только логинов. Очевидно, вам не нужно фильтровать пароли, извините, это не указано в моем ответе. Но на самом деле это может быть хорошей идеей. Использовать «неосведомленное древо дерева» вместо трудно запоминаемого типа «a4üua3! @V \» ä90; 8f »было бы гораздо сложнее, чем грубо говоря. Даже используя словарь, скажем, 3000 слов, чтобы помочь вам, зная Вы использовали ровно 4 слова - это все равно было бы примерно 3,3 * 10 ^ 12 комбинаций. :)
Слава
2
@ Слава: я видел эту идею раньше; см. xkcd.com/936 . Проблема в том, что математика не совсем справляется. Ваш пример 17-символьного пароля имел бы 96 ^ 17 возможностей, и это если вы забыли умляуты и ограничились печатным ASCII. Это примерно 4,5х10 ^ 33. Мы говорим буквально в миллиард триллионов раз больше работы для грубой силы. Даже 8-символьный пароль ASCII будет иметь 7,2x10 ^ 15 возможностей - в 3 тысячи раз больше.
cHao