Скажем, у меня есть такой код:
$dbh = new PDO("blahblah");
$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );
В документации PDO говорится:
The parameters to prepared statements don't need to be quoted; the driver handles it for you.
Это действительно все, что мне нужно сделать, чтобы избежать SQL-инъекций? Неужели это так просто?
Вы можете предположить, что MySQL имеет значение. Кроме того, мне действительно интересно только использовать подготовленные операторы против SQL-инъекции. В этом контексте меня не волнуют XSS или другие возможные уязвимости.






Лично я всегда сначала выполняю какую-либо форму очистки данных, поскольку вы никогда не можете доверять вводу пользователя, однако при использовании привязки заполнителей / параметров введенные данные отправляются на сервер отдельно от оператора sql, а затем связываются вместе. Ключевым моментом здесь является то, что это привязывает предоставленные данные к определенному типу и конкретному использованию и исключает любую возможность изменить логику оператора SQL.
Подготовленных операторов / параметризованных запросов обычно достаточно для предотвращения внедрения 1-й порядок в этот оператор *. Если вы используете непроверенный динамический sql где-либо еще в своем приложении, вы по-прежнему уязвимы для инъекции 2-й порядок.
Внедрение 2-го порядка означает, что данные были пройдены через базу данных один раз перед включением в запрос, и это намного сложнее. AFAIK, вы почти никогда не видите настоящих спроектированных атак 2-го порядка, так как злоумышленникам обычно легче проникнуть в социальную инженерию, но иногда возникают ошибки 2-го порядка из-за дополнительных безобидных символов ' или подобных.
Вы можете выполнить атаку внедрения 2-го порядка, когда вы можете сохранить значение в базе данных, которое позже будет использоваться как литерал в запросе. В качестве примера предположим, что вы вводите следующую информацию в качестве нового имени пользователя при создании учетной записи на веб-сайте (предполагая, что MySQL DB для этого вопроса):
' + (SELECT UserName + '_' + Password FROM Users LIMIT 1) + '
Если нет других ограничений на имя пользователя, подготовленный оператор все равно будет гарантировать, что указанный выше встроенный запрос не выполняется во время вставки, и правильно сохранит значение в базе данных. Однако представьте, что позже приложение извлекает ваше имя пользователя из базы данных и использует конкатенацию строк, чтобы включить это значение в новый запрос. Вы можете увидеть чужой пароль. Поскольку первые несколько имен в таблице пользователей, как правило, являются администраторами, возможно, вы также просто передали ферму. (Также обратите внимание: это еще одна причина не хранить пароли в виде простого текста!)
Таким образом, мы видим, что подготовленных операторов достаточно для одного запроса, но сами по себе они нет достаточны для защиты от атак sql-инъекций во всем приложении, поскольку им не хватает механизма для обеспечения любого доступа к базе данных в приложении, использующем безопасные код. Однако, используемые как часть хорошего дизайна приложения - который может включать такие практики, как обзор кода или статический анализ, или использование ORM, уровня данных или уровня сервиса, который ограничивает динамический sql - подготовленные заявления равно основной инструмент для решения проблемы Sql Injection. Если вы следуете хорошим принципам дизайна приложения Таким образом, ваш доступ к данным отделен от остальной части вашей программы, становится легко обеспечить или проверить правильность использования параметризации в каждом запросе. В этом случае внедрение sql (как первого, так и второго порядка) полностью предотвращается.
*It turns out that MySql/PHP are (okay, were) just dumb about handling parameters when wide characters are involved, and there is still a rare case outlined in the other highly-voted answer here that can allow injection to slip through a parameterized query.
Это интересно. Я не знал, что такое 1-й порядок против 2-го. Не могли бы вы подробнее рассказать о том, как работает 2-й порядок?
Если ВСЕ ваши запросы параметризованы, вы также защищены от внедрения второго порядка. Инъекция 1-го порядка забывает о том, что пользовательские данные ненадежны. Инъекция 2-го порядка забывает, что данные базы данных ненадежны (потому что изначально они исходили от пользователя).
Спасибо, cjm. Я также нашел эту статью полезной для объяснения инъекций 2-го порядка: codeproject.com/KB/database/SqlInjectionAttacks.aspx
О да. А как же инъекция третьего порядка. Надо знать об этом.
Да, за исключением того, что PDO по умолчанию не использует настоящие подготовленные операторы ;-). См. Мой ответ ниже для демонстрации и объяснения атаки ...
@troelskn, который должен быть там, где разработчик является источником недостоверных данных
@JoelCoehoorn Думаю, что большинство из них придут отсюда;) google.com.au/search?q=pdo+prepared+statements
Но если вы используете 100% статические подготовленные операторы во всей своей программе, я имею в виду, что весь SQL жестко закодирован в подготовленные операторы, тогда вы избегаете всех уровней внедрения, верно?
@StormByte Да, но вы просто избегаете клиентских данных.
Я не знал, что такое порядок. Читая это я подумал А как насчет 3-го и 4-го и даже больше из 5-го ?? А 6-е должно быть так смертельно опасно ...
@Pacerier Джоэл внес правку после моего комментария 8 марта, подчеркнув фразу из того, что он уже написал. Понятно, что в посте есть то, что он задумал. Я не собираюсь редактировать чей-то пост, чтобы изменить его смысл, а другие ответы уже отвечают на вопрос.
@Pacerier Я с вами не согласен. Система настроена таким образом, что мы можем редактировать любые сообщения, да, но это хороший этикет и правило - не менять радикально исходное значение.
@Brad, при условии, что "изначальный смысл" не ошибочен. Если это не так, не стесняйтесь редактировать его, чтобы исправить.
А также во втором порядке инъекции злоумышленник должен ввести имена таблиц и столбцов, которые вызывают слепую инъекцию sql ... Что ж, я делаю это .. $ username = strip_tags (mysqli_real_escape_string ($ con, $ usernam e )); после проверки длины ... а затем я также использую reg exp для проверки и, в конце концов, помещаю его с помощью PDO
@Waqas Значит, у вас разные проблемы. Что, если эти теги действительны и должны храниться в БД? Зачем использовать reg exp после real_escape? Пользовательский ввод не должен доходить до очистки БД. Проверка до прошла. Почему вы делаете mysqli_real_escape_string, а затем PDO? Конечно, ваш последний PDO делает его безопасным. если вы используете параметризованные операторы, но все дополнительные биты (бессмысленной) «безопасности», которые вы добавили, с большей вероятностью внесут брешь в безопасности, отличную от безопасности БД, или, по крайней мере, кошмарный код, который ужасно разбираться и работать.
Я не хочу позволять пользователю помещать теги в базу данных, и, пожалуйста, не могли бы вы сказать мне, что означает выражение «если эти теги действительны ...», и я сделал все это в одной функции под названием validate, которая у меня есть в мой собственный файл фреймворка веб-сайта, который я использую, чтобы сделать мой код отзывчивым и крутым
@Waqas не проверяет ввод ... карантин это. Вот что делают параметры запроса. Если вы пытаетесь защитить от чего-то другого, кроме sql-инъекции, вам нужно искать в другом месте.
Итак, «инъекция второго порядка» - это тот момент, когда люди понимают, что данным в базе данных нельзя доверять больше, чем параметрам запроса, а? Я вижу, что Даннинг-Крюгер жив и здоров в области компьютерной безопасности (как всегда).
' + (SELECT UserName + '_' + Password FROM Users LIMIT 1) + ' обычно применяется для выбора столбцов, а не значений столбцов. Если это сохранено как имя пользователя, база данных все равно будет возвращать набор результатов с этим как содержимое одной из строк. Однако мы используем их в условиях. Пожалуйста, поправьте меня. Спасибо @JoelCoehoorn
Да, этого достаточно. Атаки инъекционного типа работают так: каким-то образом заставляют интерпретатор (базу данных) оценивать что-то, что должно было быть данными, как если бы это был код. Это возможно только в том случае, если вы смешиваете код и данные на одном носителе (например, когда вы строите запрос в виде строки).
Параметризованные запросы работают, отправляя код и данные отдельно, так что никогда может найти в них брешь.
Однако вы все еще можете быть уязвимы для других атак типа инъекций. Например, если вы используете данные на HTML-странице, вы можете подвергнуться атакам типа XSS.
«Никогда» - это путь завышает это до такой степени, что вводит в заблуждение. Если вы неправильно используете подготовленные операторы, это не намного лучше, чем их вообще не использовать. (Конечно, «подготовленный оператор», в который был введен пользовательский ввод, противоречит цели ... но я действительно видел это выполненным. А подготовленные операторы не могут обрабатывать идентификаторы (имена таблиц и т. д.) В качестве параметров.) Добавить к этому некоторые из драйверов PDO подражать подготовили операторы, и у них есть возможность сделать это неправильно (например, наполовину проанализировав SQL). Краткая версия: никогда не думайте, что это так просто.
Нет, не всегда.
Это зависит от того, разрешаете ли вы размещать вводимые пользователем данные в самом запросе. Например:
$dbh = new PDO("blahblah");
$tableToUse = $_GET['userTable'];
$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );
будут уязвимы для SQL-инъекций, и использование подготовленных операторов в этом примере не сработает, потому что вводимые пользователем данные используются как идентификатор, а не как данные. Правильный ответ здесь - использовать какую-то фильтрацию / проверку, например:
$dbh = new PDO("blahblah");
$tableToUse = $_GET['userTable'];
$allowedTables = array('users','admins','moderators');
if (!in_array($tableToUse,$allowedTables))
$tableToUse = 'users';
$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );
Примечание: вы не можете использовать PDO для привязки данных, выходящих за пределы DDL (языка определения данных), т.е. это не работает:
$stmt = $dbh->prepare('SELECT * FROM foo ORDER BY :userSuppliedData');
Причина, по которой вышеуказанное не работает, заключается в том, что DESC и ASC не являются данные. PDO может быть экранирован только для данные. Во-вторых, вокруг него даже нельзя ставить кавычки '. Единственный способ разрешить сортировку, выбранную пользователем, - это вручную отфильтровать и проверить, что это DESC или ASC.
Я что-то упустил, но разве весь смысл подготовленных операторов не в том, чтобы рассматривать sql как строку? Разве что-то вроде $ dbh-> prepare ('SELECT * FROM: tableToUse where username =: username'); обойти свою проблему?
@RobForrest да вы пропали :). Связываемые данные работают только для DDL (языка определения данных). Вам нужны эти кавычки и правильное экранирование. Размещение кавычек для других частей запроса с большой вероятностью нарушит его. Например, SELECT * FROM 'table' может быть неправильным, так как он должен быть SELECT * FROM `table` или без каких-либо задников. Тогда некоторые вещи, такие как ORDER BY DESC, где DESC исходит от пользователя, не могут быть просто экранированы. Итак, практических сценариев можно считать неограниченными.
Интересно, как 6 человек могли проголосовать за комментарий, предлагающий явно неправильное использование подготовленного заявления. Если бы они хотя бы раз попробовали, то сразу обнаружили бы, что использование именованного параметра вместо имени таблицы не сработает.
Вот отличный учебник по PDO, если вы хотите его изучить. a2znotes.blogspot.in/2014/09/introduction-to-pdo.html
Вы никогда не должны использовать строку запроса / тело POST для выбора таблицы для использования. Если у вас нет моделей, по крайней мере, используйте switch для получения имени таблицы.
если вам нужно динамически выбирать имя таблицы, всегда есть другие инструменты, такие как sprintf. Просто создайте там подготовленный шаблон оператора, а затем только подготовьте сгенерированный шаблон оператора.
@RobForrest Я знаю эти даты с давних пор, но да, действительно, вам чего-то не хватает :) Это не сработает (вы также могли бы попробовать). Подготовка оператора означает, что ядро базы данных будет планировать запросы. Для этого необходимо знать, что будет использоваться какие таблицы, какие столбцы будут выбраны и так далее. Вы не можете параметризовать имя таблицы, это не имеет смысла.
Нет, этого недостаточно (в некоторых конкретных случаях)! По умолчанию PDO использует эмулированные подготовленные операторы при использовании MySQL в качестве драйвера базы данных. Вы всегда должны отключать эмулируемые подготовленные операторы при использовании MySQL и PDO:
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Еще одна вещь, которую всегда следует делать, - это установить правильную кодировку базы данных:
$dbh = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');
Также см. Этот связанный вопрос: Как я могу предотвратить SQL-инъекцию в PHP?
Также обратите внимание, что это касается только той стороны базы данных, на которую вам все равно придется следить при отображении данных. Например. снова используя htmlspecialchars() с правильным стилем кодирования и цитирования.
Eaven, если вы собираетесь предотвратить внешний интерфейс sql-инъекций, используя проверки html или js, вам придется учитывать, что проверки внешнего интерфейса «обходятся».
Вы можете отключить js или отредактировать шаблон с помощью интерфейсного инструмента разработки (в настоящее время встроенного в firefox или chrome).
Итак, чтобы предотвратить SQL-инъекцию, было бы правильно очистить серверную часть даты ввода внутри вашего контроллера.
Я хотел бы предложить вам использовать встроенную функцию PHP filter_input () для очистки значений GET и INPUT.
Если вы хотите обеспечить безопасность для разумных запросов к базе данных, я хотел бы предложить вам использовать регулярное выражение для проверки формата данных. preg_match () поможет вам в этом случае! Но будьте осторожны! Движок Regex не такой уж легкий. Используйте его только при необходимости, иначе производительность вашего приложения снизится.
За безопасность приходится платить, но не тратьте зря свои результаты!
Простой пример:
если вы хотите дважды проверить, является ли значение, полученное от GET, числом меньше 99 если (! preg_match ('/ [0-9] {1,2} /')) {...} тяжелее
if (isset($value) && intval($value)) <99) {...}
Итак, окончательный ответ: «Нет! Подготовленные операторы PDO не предотвращают все виды SQL-инъекций»; Это не предотвращает неожиданные значения, просто неожиданное объединение
Вы путаете SQL-инъекцию с чем-то еще, что делает ваш ответ совершенно неуместным.
лучше подойти 7-е число ответ stackoverflow.com/questions/134099/…