Я всегда делал простое соединение 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 – экранирование, а просто ограничители строк . Из этого определения вы можете заключить его ограничения – он работает только для строк .
однако он по-прежнему уязвим для других атак, которые могут содержать безопасные символы, но могут быть вредны для отображения данных или, в некоторых случаях, для изменения или удаления данных злонамеренно.
Вы все здесь смешиваете.
Говоря о базе данных,
*
Что касается отображаемых данных, я полагаю, что это оффтопный вопрос, связанный с PDO, поскольку PDO не имеет ничего общего с отображением данных.
выход из пользовательского ввода
^^^ Еще одно заблуждение, которое нужно отметить!
пользовательский ввод абсолютно не имеет никакого отношения к экранированию . Как вы можете узнать из предыдущего определения, вам нужно избегать строк, а не «пользовательский ввод». Итак, снова:
Понял?
Теперь я надеюсь, что вы понимаете ограничения выхода, а также ошибочное представление «опасных персонажей».
По моему мнению, использование 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 в блок 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 ).