Перейти к содержанию
Авторизация  

[PHP] MySQLi - осваиваем mysqli_prepare (Подготовленные запросы)

   (1 отзыв)

Всем доброго времени суток! В этом уроке мы с вами будем учиться использовать mysqli_prepare который сделает наш скрипт куда современнее + защитит от простых SQL инъекций.

Что это и зачем это нужно

Начнем с того, что MySQL перестал поддерживаться уже давным давно, на смену ему пришел MySQLi. Он стал куда сочнее и функциональнее да и пользоваться в целом им стало удобнее. Мы с вами остановимся конкретно на mysqli_prepare.

Цитата

mysqli_prepare - Подготавливает SQL запрос и возвращает указатель на это выражение, который может использоваться для дальнейших операций с этим выражением. Запрос должен состоять из одного SQL выражения. ((Взято с php.net))

 

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

"; DROP TABLE l2jserver; #

У него ничего не получится, ведь подготовленный запрос должен состоять из одного SQL выражения!

Рассмотрим на конкретном примере

Давайте представим что у нас стоит обычный MySQL и перед регистрацией аккаунта наш скрипт проверяет наличие аккаунта перед тем как его добавить (или  вернуть ошибку что аккаунт существует).

В таком случае PHP код регистрации будет выглядеть примерно так:

<?php
$connect=mysql_connect($host, $user, $password); // иницилизируем подключение
mysql_select_db($connect, $dbname); // выбираем нужную бд
$query='SELECT * FROM accounts WHERE login="'.$login.'"'; // объявляем переменную с запросом
$result=mysql_query($query); // выполняем запрос
$rows=mysql_num_rows($result); // получаем кол-во строк из результата
if($rows) { // если строки найдены
	// аккаунт уже есть, выдаем ошбку;
} else { // если строки не найдены
	// аккаунта нет, можем регистрировать;
}
mysql_close($connect); // закрываем подключение
?>

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

Тогда вместо логина я должен указать: "; DROP DATABASE l2jserver; #

# - нужна чтобы MySQL игнорировал всё что идёт после неё.

Таким образом $query будет выглядеть следующим образом:

$query='SELECT * FROM accounts WHERE login=""; DROP DATABASE l2jserver; #"';

За ним последует выполнение функции mysql_query и как следствие дроп базы сервера.

Как этого избежать? - Юзай MySQLi!

Теперь предлагаю рассмотреть код регистрации который мы привели выше с использованием MySQLi (без prepare).

<?php
	$connect=new mysqli($host, $user, $password, $db); // иницилизируем подключение к бд
	$query='SELECT * FROM accounts WHERE login="'.$_POST['login'].'"'; // объявляем переменную с запросом
	$result=$connect->query($query); // выполняем запрос
	$rows=$result->num_rows; // получаем кол-во строк в полученном результате из запроса
	if($rows) { // если строки найдены
	// такой аккаунт уже найден выводим ошибку;
	} else { // если строки не найдены
	// аккаунт не найден, регистрируем;
	}
	$result->free(); // очищаем результат
	$connect->close(); // закрываем подключение
?>

Но этот скрипт до сих пор не безопасен, поскольку мы так же легко выполним два запроса одной функцией. Чтобы этого избежать будем использовать mysqli_prepare. Поехали!)

<?php
	$connect=new mysqli($host, $user, $password, $db); // иницилизируем подключение к бд
	$query='SELECT * FROM accounts WHERE login=?'; // объявляем переменную с запросом
	$stmt = $connect->prepare($query); // подготавливаем наш запрос
	$stmt->bind_param('s', $_POST['login']); // присваеваем первому ? в запросе параметр с типом данных s (string)
	$stmt->execute(); // выполняем подготовленный запрос
	$result=$stmt->get_result(); // получаем результат из подготовленного запроса
	$rows=$result->num_rows; // получаем кол-во строк в полученном результате из запроса
	if($rows) { // если строки найдены
	// такой аккаунт уже найден выводим ошибку;
	} else { // если строки не найдены
	// аккаунт не найден, регистрируем;
	}
	$result->free(); // очищаем результат
	$stmt->close(); // закрываем подготовленный запрос
	$connect->close(); // закрываем подключение
?>

// P.S. Я сам только сейчас осваиваю эту функцию и поэтому для усвоения решил написать этот мануал. Поэтому если что то не так - поправьте.

Теперь давайте по порядку. Мы можем забиндить (функция bind_param) сколько угодно параметров любого типа данных. Главное чтобы мы их объявляли по порядку исходя из нашего запроса. В $query где login=? мы даем понять коду что мы хотим вместо ? подставить параметры которые мы присвоили ниже функцией bind_param. Таким образом мы можем строить куда более сложные запросы и использовать разные типы данных. Как в примере ниже:

<?php
  $accesslevel=0;
  $stmt = $mysqli->prepare( "INSERT INTO accounts ( login, password, accesslevel ) VALUES (?,?,?)" );
  $stmt->bind_param( "ssi", $_POST['char_name'], $_POST['password'], $accesslevel );
  $stmt->execute();
  $stmt->close();
?>

Что у нас получается. В наш запрос будут подставлены данные из переменных с тем типом данных которые мы указали перед первой запятой в кавычках.

i - int (целое число)

s - string (строковое значение)

 

Теперь когда наш школохакер захочет выполнить sql инъекцию система просто не даст ему это сделать, поскольку подготовленный запрос может быть только один. Для подстраховки вы можете так же использовать функцию экранирования mysqli->real_escape_string($string); перед тем как подставить эти данные в запрос, но это для супер параноиков т.к. prepare автоматически экранирует данные.

Надеюсь урок будет полезен и расширит ваш кругозор!) Не откажусь от благодарности.

Цитата

Оригинальный мануал написан пользователем kickuass и опубликован на форуме forummaxi.ru. Копирование без ссылки на источник запрещено!

Урок создан в ознакомительных целях! Автор (kickuass) не несет ответственность за последствия использования пользователями информации из этого урока.

Всем спасибо за внимание!

  • Upvote 3


Рекомендуемые комментарии

". Для подстраховки вы можете так же использовать функцию экранирования mysqli->real_escape_string($string); перед тем как подставить эти данные в запрос, но это для супер параноиков т.к. prepare автоматически экранирует данные."

Господи.... Использывание real_escape_string уже не нужно. Только понизите производительность. Сначала написали что он автоматически очищается а потом сказали о дополнительной защите это просто что-то...

Как по мне статья бесполезная на много лучше читать офф док.

Поделиться этим комментарием


Ссылка на комментарий
Поделиться на другие сайты

Огорчу многих MySQL похоронили, и MySQLi за им пойдет через пару лет я более чем уверен.

В 2к18 году как по мне только один класс для работы с бд в PHP это PDO

 

Изменено пользователем Makzz
  • Like 1
  • Upvote 1

Поделиться этим комментарием


Ссылка на комментарий
Поделиться на другие сайты

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

  • Like 1

Поделиться этим комментарием


Ссылка на комментарий
Поделиться на другие сайты
×
×
  • Создать...