Если не обрабатывать данные, которые вводит пользователь в форму отправки данных или в адресную строку, и которые затем необработанными отправляются в базу данных, то можно столкнуться с риском получить SQL инъекцию в базу данных.
// Никак не обрабатываем данные, получаемые от пользователей $unsafe_variable = $_POST['user_input']; // Затем эти данные отправляем в базу данных mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");Опасность тут заключается в следующем: если пользователь отправит данные вроде таких:
value'); DROP TABLE table_name;--
, то итоговый запрос будет выглядеть так:INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table_name;--')Этот запрос удалит таблицу
table_name
.
Чтобы избежать подобной проблемы, можно воспользоваться следующими рекомендациями:
Использовать заранее подготовленные данные
Можно воспользоваться подготовленными операторами и обработанными таким образом запросами. Такие операторы SQL позволят вам определять типы передаваемых данных, фильтровать их.
Есть 2 варианта их использования:
- Используем PDO (PHP Data Objects):
// Конфигурируем будущий запрос, $q = $pdo->prepare('SELECT * FROM table_name WHERE name = :name'); // Выполняем запрос, установив связь с введёнными данными // $name - переменная с данными, которые связываются с оператором name $q->execute(array('name' => $name)); foreach ($q as $e) { // Производим манипуляции с результатами $e }
- Для MySQL используем MySQLi
$q = $db->prepare('SELECT * FROM table_name WHERE name = ?'); // $name - переменная с данными, которые будут обрабатываться $q->bind_param('s', $name); // 's' определяет тип переменной => 'string' (строка) $q->execute(); $result = $q->get_result(); while ($row = $result->fetch_assoc()) { // Производим манипуляции с $row }
Если в роли базы данных используется не MySQL, то в этом случае нужно изучить документацию на предмет использования аналогичных методов.
Например, в PostgreSQL есть
pg_prepare()
andpg_execute()
.
Установить соединение с базой данных правильно
PDO по умолчанию эмулирует работу с подготовленными операторами для работы с MySQL. Поэтому важно отключить режим эмуляции, например так:
// Устанавливаем соединение $db = new PDO('mysql:dbname=mydatabase;host=127.0.0.1;charset=utf8', 'db_login', 'db_pass'); // Отключаем режим эмуляции для обработки данных // Включаем рабочий реальный режим работы PDO $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); // Включаем режим обработки ошибок и исключений // PHP Fatal error не будут обрывать работу скрипта, а исключения позволят отлавливать ошибки $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Готовим операторы // :column $q = $db->prepare('INSERT INTO table (column) VALUES (:column)'); // Связываем операторы с потенциально опасными данными // которые будут обработаны и отфильтрованы $q->execute(array('column' => $danger_data));
Важный момент касаемо параметра charset: версии PHP < 5.3.6 молча игнорируют его
Как это всё работает
Указывая параметр ?
или именованный :name
из примера выше, можно сообщить базе данных, что из данных должно быть обработано и отфильтровано. Затем во время вызова execute()
указываются данные, которые будут проверяться.
SQL-инъекция работает через обман скрипта, когда к строке запроса в базу данных подмешиваются вредоносные строки кода. PDO работает по принципу префильтрации данных перед отправкой запроса, тем самым предотвращая риск исполнения запроса, который не планировался. Любые параметры, отправляемые в базу данных, будут обрабатываться как строки (или числа, в зависимости от заданных параметров запроса), поэтому никаких неожиданных подзапросов не произойдёт.
Ещё одно неоспоримое преимущество PDO состоит в том, что если использовать оператор несколько раз в коде, то он скомпилируется 1 раз, а в дальнейшем будет использоваться ранее скомпилированная версия, что даст экономию по ресурсам и увеличит быстродействие скрипта.
Как использовать с динамическими запросами
Вы можете использовать следующий подход (составить белый лист параметов и пропускать только их):
// $order может быть 'DESC' // Во всех остальных случаях $order это 'ASC' if (empty($order) || $order !== 'DESC') { $order = 'ASC'; }
Свежие комментарии