Замена функций mysql * на PDO и подготовленные операторы

я всегда делал простое соединение mysql_connect, mysql_pconnect:

$db = mysql_pconnect('*host*', '*user*', '*pass*');

if (!$db) {
    echo("<strong>Error:</strong> Could not connect to the database!");
    exit;
}

mysql_select_db('*database*');

при использовании этого я всегда использовал простой метод, чтобы избежать любых данных перед выполнением запроса, будь то INSERT, SELECT, UPDATE или DELETE С помощью mysql_real_escape_string

$name = $_POST['name'];

$name = mysql_real_escape_string($name);

$sql = mysql_query("SELECT * FROM `users` WHERE (`name` = '$name')") or die(mysql_error());

теперь я понимаю, что это безопасно, до некоторой степени!

он избегает опасных символов; однако он по-прежнему уязвим для других атак, которые могут содержать безопасные символы, но могут быть вредно для отображения данных или в некоторых случаях, изменения или удаления данных злонамеренно.

Итак, я немного поискал и узнал о PDO, MySQLi и подготовленных заявлениях. Да, я могу опоздать на игру, но я прочитал много, много учебников (tizag, W3C, блоги, поисковые запросы Google), и ни один из них не упомянул об этом. Кажется очень странным, почему, так как просто избежать ввода пользователя действительно небезопасно и, мягко говоря, не рекомендуется. Да, я знаю, что ты можешь. используйте Regex, чтобы справиться с этим, но все же, я уверен, что этого недостаточно?

насколько я понимаю, использование PDO / подготовленных операторов-гораздо более безопасный способ хранения и извлечения данных из базы данных, когда переменные задаются вводом пользователя. Единственная проблема в том, что переключение (особенно после того, как я очень застрял в моих способах/привычках предыдущего кодирования) немного сложно.

прямо сейчас я понимаю, что для подключения к моей базе данных с помощью PDO я бы использовать

$hostname = '*host*';
$username = '*user*';
$password = '*pass*';
$database = '*database*'

$dbh = new PDO("mysql:host=$hostname;dbname=$database", $username, $password);

if ($dbh) {
    echo 'Connected to database';
} else {
    echo 'Could not connect to database';
}

теперь имена функций разные, поэтому больше не будет мой mysql_query, mysql_fetch_array, mysql_num_rows работа etc. Поэтому мне приходится читать / запоминать массу новых, но именно здесь я запутываюсь.

если бы я хотел вставить данные, скажем, из формы регистрации/регистрации, как бы я это сделал, но в основном как бы я это сделал безопасно? Я предполагаю, что здесь входят подготовленные заявления, но, используя их, это устраняет необходимость использовать что-то как mysql_real_escape_string? Я знаю это mysql_real_escape_string требуется подключение к базе данных через mysql_connect/mysql_pconnect Итак, теперь мы не используем ни одну, не будет ли эта функция просто производить ошибку?

я тоже видел разные способы подхода к методу PDO, например, я видел :variable и ? как я думаю, известны как держатели мест (извините, если это неправильно).

но я думаю, что это примерно идея того, что нужно сделать, чтобы получить пользователя от база данных

$user_id = $_GET['id']; // For example from a URL query string

$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");

$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);

но тогда я застрял на пару вещей, если переменная не была числом и была строкой текста, вы должны дать длину после PDO:PARAM_STR если не ошибаюсь. Но как вы можете дать длину набора, если вы не уверены в значении, заданном из пользовательских данных, оно может меняться каждый раз? В любом случае, насколько я знаю, чтобы отобразить данные, которые вы тогда делаете

$stmt->execute();

$result = $stmt->fetchAll();

// Either

foreach($result as $row) {
    echo $row['user_id'].'<br />';
    echo $row['user_name'].'<br />';
    echo $row['user_email'];
}

// Or

foreach($result as $row) {
    $user_id = $row['user_id'];
    $user_name = $row['user_name'];
    $user_email = $row['user_email'];
}

echo("".$user_id."<br />".$user_name."<br />".$user_email."");

теперь, это все безопасно?

если я прав, вставка данных будет же например:

 $username = $_POST['username'];
 $email = $_POST['email'];

 $stmt = $dbh->prepare("INSERT INTO `users` (username, email)
                        VALUES (:username, :email)");

 $stmt->bindParam(':username, $username, PDO::PARAM_STR, ?_LENGTH_?);
 $stmt->bindParam(':email, $email, PDO::PARAM_STR, ?_LENGTH_?);

$stmt->execute();

это сработает, и это тоже безопасно? Если это правильно, какое значение я бы поставил для ?_LENGTH_?? Неужели я все неправильно понял?

обновление

ответы, которые у меня были до сих пор, были очень полезны, не могу поблагодарить вас, ребята, достаточно! У каждого есть +1 для открытия моих глаз на что-то немного другое. Трудно выбрать лучший ответ, но я думаю, что полковник шрапнель заслуживает этого, поскольку все в значительной степени покрыто, даже переходя в другие массивы с пользовательскими библиотеками, о которых я не знал!

но спасибо всем вам:)

4 ответов


Спасибо за интересный вопрос. Вот тебе:

Он избегает опасных персонажей,

ваша концепция совершенно неправильно.
На самом деле" опасные персонажи " - это миф, их нет. И mysql_real_escape_string убегает, но просто строку разделители. Из этого определения вы можете заключить, что это ограничения - он работает только для строки.

, это все еще уязвимы для других атак, которые могут содержать безопасные символы, но могут быть вредны для отображения данных или в некоторых случаях, изменения или удаления данных злонамеренно.

вы смешиваете здесь все.
Говоря о базе данных,

  • для строк это не уязвимым. пока ваши строки цитируются и сбежал, они не может "злонамеренно изменять или удалять данные".*
  • для другие данные typedata-да, это бесполезно. Но не потому, что это несколько "небезопасно", а просто из-за неправильного использования.

что касается отображения данных, я полагаю, это offtopic в вопросе, связанном с PDO, as PDO не имеет ничего общего с отображением данных.

экранирование пользовательского ввода

^^^ еще одно заблуждение следует отметить!

  • пользователь вход имеет абсолютно ничего общего с побегом. Как вы можете узнать из предыдущего определения, вы должны избегать строк, а не какой-либо "пользовательский ввод". Итак, снова:

    • у вас есть escape-строки, независимо от их источника
    • бесполезно избегать других типов данных, независимо от источника.

понял?
Теперь, я надеюсь, вы понимаете ограничения побега, а также "опасные персонажи" заблуждение.

насколько я понимаю, использование PDO / подготовленных заявлений намного безопаснее

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

  • строка
  • ряд
  • идентификатор
  • синтаксис ключевого слова.

Итак, вы можете видеть, что экранирование охватывает только одну проблему. (но конечно, если вы рассматриваете числа как строки (помещая их в кавычки), при необходимости, вы также можете сделать их безопасными)

в то время как подготовленные заявления охватывают - тьфу - целых 2 isues! Большое дело ;-)

для других 2 вопросов см. мой предыдущий ответ,в PHP при отправке строк в базу данных следует ли заботиться о незаконных символах с помощью htmlspecialchars () или использовать регулярное выражение?

теперь имена функций другой, поэтому больше не будет работать мой mysql_query, mysql_fetch_array, mysql_num_rows и т. д.

это другой, серьезное заблуждение PHP пользователи, стихийное бедствие, катастрофа:

даже при использовании старого драйвера MySQL, никогда не следует использовать голые функции API в своем коде! Нужно поместить их в некоторую библиотечную функцию для повседневного использования! (Не как какой-то магический обряд, а просто чтобы сделать код короче, меньше повторяющийся, безошибочный,более последовательный и читаемый).

то же самое относится и к PDO!

теперь снова ваш вопрос.

но используя их, это устраняет необходимость использовать что-то вроде mysql_real_escape_string?

да.

но я думаю, что это примерно идея того, что нужно сделать, чтобы получить пользователя из базы данных

не принести, но добавить любые данные в запрос!

вы должны дать длину после PDO: PARAM_STR, если я не ошибаюсь

вы можете, но вы не должны.

теперь, это все безопасно?

С точки зрения безопасности базы данных в этом коде просто нет слабых мест. Здесь нечего охранять.

для отображения безопасности-просто найдите этот сайт для XSS ключевое слово.

Надежда Я пролил свет на этот вопрос.

кстати, для длинных вставок вы можете использовать функцию, которую я написал когда-нибудь, вставить / обновить вспомогательную функцию с помощью PDO

однако, я не использую подготовленные заявления на данный момент, так как я предпочитаю мои домашние заполнители над ними, используя библиотека я упомянул выше. Таким образом, чтобы противостоять коду, опубликованному riha ниже, он будет таким же коротким, как эти 2 строки:

$sql  = 'SELECT * FROM `users` WHERE `name`=?s AND `type`=?s AND `active`=?i';
$data = $db->getRow($sql,$_GET['name'],'admin',1);

а конечно, вы можете иметь тот же код, используя подготовленные операторы.


* (yes I am aware of the Schiflett's scaring tales)


Я никогда не беспокоюсь о типах или длинах bindParam () или param.

Я просто передаю массив значений параметров для выполнения (), например:

$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");
$stmt->execute( array(':user_id' => $user_id) );

$stmt = $dbh->prepare("INSERT INTO `users` (username, email)
                        VALUES (:username, :email)");
$stmt->execute( array(':username'=>$username, ':email'=>$email) );

Это так же эффективно и проще в коде.

вы также можете быть заинтересованы в моей презентации SQL инъекции мифы и заблуждения, или моей книги SQL Antipatterns: избегая подводных камней программирования баз данных.


Да,: что-то называется заполнителем в PDO,? является анонимным заполнителем. Они позволяют связывать значения по одному или все сразу.

Итак, в основном это делает четыре варианта для предоставления вашему запросу значений.

С bindValue()

это связывает конкретное значение на свой прототип, как только вы его называете. Вы даже можете связать жестко закодированные строки, такие как bindValue(':something', 'foo') Если желанный.

указание типа параметра необязательна (но желательна). Однако, поскольку значение по умолчанию PDO::PARAM_STR, вам нужно только указать его, когда это не строка. Кроме того,PDO позаботится о длине здесь - нет параметра длины.

$sql = '
  SELECT *
  FROM `users`
  WHERE
    `name` LIKE :name
    AND `type` = :type
    AND `active` = :active
';
$stm = $db->prepare($sql);

$stm->bindValue(':name', $_GET['name']); // PDO::PARAM_STR is the default and can be omitted.
$stm->bindValue(':type', 'admin'); // This is not possible with bindParam().
$stm->bindValue(':active', 1, PDO::PARAM_INT);

$stm->execute();
...

обычно я предпочитаю такой подход. Я нахожу его самым чистым и гибким.

С bindParam()

переменной обязан ваш заполнитель, который будет считываться при запросе выполнена, не при вызове bindParam (). Это может быть или не быть тем, чего вы хотите. Это удобно, когда вы хотите повторно выполнить свой запрос с разными значениями.

$sql = 'SELECT * FROM `users` WHERE `id` = :id';
$stm = $db->prepare($sql);
$id = 0;
$stm->bindParam(':id', $id, PDO::PARAM_INT);

$userids = array(2, 7, 8, 9, 10);
foreach ($userids as $userid) {
  $id = $userid;
  $stm->execute();
  ...
}

вы только подготовить и связать один раз, какие сейфы CPU циклов. :)

все сразу с именем заполнители

вы просто бросаете массив в execute(). Каждый ключ имеет имя заполнитель в запросе (см. Билл Karwins ответа). Порядок выбора не важен.

на стороне Примечание: при таком подходе вы не можете предоставить PDO подсказки типа данных (PDO::PARAM_INT и т. д.). AFAIK, PDO пытается угадать.

все сразу с анонимными заполнители

вы также вводите массив для выполнения (), но он численно индексируется (не имеет строковых ключей). Значения будут заменить анонимный заполнителей в порядок их появления в вашем запросе / массиве - первое значение массива заменяет первый заполнитель и так далее. Увидеть erm410 ответ.

как и в массиве и именованных заполнителях, вы не можете предоставить подсказки типа данных.

что у них общего

  • все они требуют, чтобы вы связали/предоставили столько значений, сколько у вас есть подстановки. Если вы свяжете слишком много / мало, PDO съест ваших детей.
  • вам не нужно заботиться о убегая, PDO справляется с этим. Подготовленные операторы PDO безопасны для инъекций SQL по дизайну. Однако, это не относится к exec () и query () - вы обычно должны использовать только эти два жестко запросы.

также имейте в виду, что PDO выбрасывает исключения. Они могут раскрывать потенциально конфиденциальную информацию для пользователя. Вы должны, по крайней мере, положить свой начальная настройка PDO в блоке try/catch!

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

try {
  $db = new PDO(...);
  $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING)
} catch (PDOException $e) {
  echo 'Oops, something went wrong with the database connection.';
}

чтобы ответить на вопрос длины, укажите его необязательно, если параметр, который вы связываете, не является параметром OUT из хранимой процедуры, поэтому в большинстве случаев вы можете безопасно опустить его.

что касается безопасности, экранирование выполняется за кулисами при привязке параметров. Это возможно, поскольку при создании объекта необходимо было создать соединение с базой данных. Вы также защищены от атак SQL-инъекций, так как при подготовке инструкции вы сообщаете своей базе данных формат оператора перед вводом пользователем может приблизиться к нему. Пример:

$id = '1; MALICIOUS second STATEMENT';

mysql_query("SELECT * FROM `users` WHERE `id` = $id"); /* selects user with id 1 
                                                          and the executes the 
                                                          malicious second statement */

$stmt = $pdo->prepare("SELECT * FROM `users` WHERE `id` = ?") /* Tells DB to expect a 
                                                                 single statement with 
                                                                 a single parameter */
$stmt->execute(array($id)); /* selects user with id '1; MALICIOUS second 
                               STATEMENT' i.e. returns empty set. */

таким образом, с точки зрения безопасности, ваши примеры выше, кажется, хорошо.

наконец, я согласен, что привязка параметров индивидуально утомительна и так же эффективно выполняется с массивом, переданным PDOStatement - >execute () (см. http://www.php.net/manual/en/pdostatement.execute.php).