Подтвердить что ты не робот

Замена функций 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 и т.д. Поэтому мне приходится читать/запоминать загрузку новых, но здесь я смущаюсь.

Если бы я хотел вставить данные из формы регистрации/регистрации, как бы я это сделал, но в основном, как бы я мог сделать это безопасно? Я предполагаю, что это то, где приходят подготовленные заявления, но, используя их, это устраняет необходимость использовать что-то вроде 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_?? У меня все это было совершенно неправильно?

UPDATE

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

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

4b9b3361

Ответ 1

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

Он избегает опасных символов,

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

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

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

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

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

выход из пользовательского ввода

^^^ Еще одно заблуждение, которое нужно отметить!

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

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

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

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

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

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

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

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

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

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

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

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

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

Теперь повторю вопрос.

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

ДА.

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

Не извлекать, а добавлять какие-либо данные в запрос!

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

Вы можете, но вам этого не нужно.

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

В плане безопасности базы данных в этом коде отсутствуют только слабые места. Здесь ничего нет.

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

Надеюсь, что я пролил свет на этот вопрос.

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

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

$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 scaring tales)

Ответ 2

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

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

$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 Injection Myths and Fallacies, или моей книге SQL Antipatterns: избежание ошибок программирования базы данных.

Ответ 3

Да, что-то является именованным заполнителем в 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(). Каждый ключ является именованным заполнителем в вашем запросе (см. Ответ Билла Карвинса). Порядок массива не имеет значения.

С одной стороны: при таком подходе вы не можете предоставить 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.';
}

Ответ 4

Чтобы ответить на вопрос о длине, указание его необязательно, если параметр, который вы связываете, является параметром 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).