Intereting Posts
Лучший способ повторить имя пользователя на каждой странице с помощью сеансов? цитируя константы в php: «это MY_CONSTANT» Regex сопоставляет img-тег с определенным классом атрибутов Запрос на обновление активной записи Codigniter, беря старое, где предложение Сколько переменных приходится на многие при хранении в _SESSION? PHP – удалить последний символ файла Можно ли изменить $ _ в php? Есть ли идиоматический способ получить потенциально неопределенный ключ из массива в PHP? Перенос приложения php для обработки UTF-8 Как получить идентификатор адреса доставки / биллинга заказа за пределами API-интерфейса Magento? проблема с cookie в PHP и AJAX Переменные продукты WooCommerce: сохраняйте только «минимальную» цену с помощью специальной метки MySQL Возвращает различное количество строк на localhost vs live server для одного и того же кода Автоматизация работы на рабочем месте: импорт текста пули PowerPoint в лист Excel Ошибка сеанса после перезагрузки страницы

Замена функций 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_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_? ? Неужели я все это совершенно не так?

ОБНОВИТЬ

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

Но спасибо всем вам 🙂

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

в то время как подготовленные заявления охватывают – тьфу – целые 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

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

 $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 () или параметрах или длинах.

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

 $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: избегайте ошибок программирования баз данных .

Да, что-то является именованным заполнителем в 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(); ... - $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(); ... } - $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(); ... } 

Вы только подготовляете и связываете один раз, который защищает циклы процессора. 🙂

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

Вы просто забрасываете массив для 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.'; } 

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

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

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