Статьи ⇒ Базы данных ⇒ Защита от SQL-инъекций

Защита от SQL-инъекций

Опубликовано: 4 апр 2011 в 23:34
Перевод: freeeeez 

Что такое SQL-инъекция?

SQL-инъекция — это процесс, при котором кто-либо выполняет SQL-запрос к базе данных без вашего ведома, с целью нанесения ущерба. Данная техника заключается в эксплуатации уязвимостей программного кода. Это происходит только тогда, когда ваши запросы составлены с использованием внешних данных, которые принимаются от пользователя. В данной статье представлены техники защиты от SQL-инъекций. Я проиллюстрирую эту ситуацию в двух случаях, (1) когда данные, введенные пользователем, плохо фильтруются, и (2) когда они не соответствуют типу принимаемых данных и выполняется один или несколько запросов.

Это становится возможным, когда у вас отсутствует фильтрация входных данных, ведь позователь может ввести все что угодно, даже дополнительные запросы к БД.

Пример 1. Выполнение одного SQL-запроса
<?php

$sqlStatement = "SELECT * FROM customers where username='james';";

?>
Пример 2. Выполнение более нескольких SQL-запросов
<?php

$sqlStatement = "DROP TABLE users; UPDATE customers SET age=0; DELETE FROM customers where id>0;";

?>
Теперь злоумышленник может объединить оба случая, выполнить запросы одновременно, удалить таблицу users и повредить таблицу customers.

В результате успешной SQL-инъекции наносится вред базе данных.

Причины SQL-инъекции

SQL-инъекция может возникнуть в следующих случаях:

  • Отсутствие фильтрации
  • Неправильная обработка типов
  • Уязвимости в базе данных сервера
  • Условные ошибки

1. Отсутствие фильтрации

Представим, что есть модуль, который запрашивает адрес электронной почты пользователя, чтобы отправить ему временный пароль к почте, когда он забывает свой пароль. В этом случае, SQL-запрос выглядеть, примерно, так:
<?php

$sqlStatement = "SELECT * FROM users WHERE username = '" + $username + "' AND email = '" + $email + "' ";

?>
Но хакер может изменить этот запрос, если установит значение переменной $email, добавив к адресу электронной почты удаление таблицы users:

user@hostname.com’; DROP TABLE users; SELECT * FROM customers WHERE name LIKE ‘%

В результате конечный запрос будет выглядеть так:
<?php

$sqlStatement = "SELECT * FROM users WHERE username = 'james' AND email = 'user@hostname.com'; DROP TABLE users; SELECT * FROM customers WHERE name LIKE '%'";

?>
Видите, как легко можно удалить любую таблицу из базы данных. Как результат, ваша система зависнет, поскольку ни один юзер не сможет пройти аутентификацию с этого момента. Если у вас нет резервной копии базы данных, вы потеряете все.

2. Неправильная обработка типов

Часто вы знаете тип данных, которые хотите получить. Например, возраст клиентов является числом, пол пользователя (муж/жен) строкой.

А что, если кто-то введет $ageValue, как:

20; DROP TABLE users

В результате SQL запрос приобретет следующий вид:
<?php

$sqlStatement = "SELECT * FROM customers WHERE age = 20; DROP TABLE users;";

?>
Вы точно знаете, что значение $ageValue всегда будет числом. И чтобы злоумышленник не смог ввести что-то другое, необходимо проверять эту переменную.

3. Уязвимости в базе данных сервера

Хотя многие думают, что могут избежать SQL-инъекций, просто используя mysql_real_escape_string(), но они не правы, к сожалению. Встроенные функции для работы с базами данных поставляются вместе с языковым пакетом, а в некоторых прошлых версиях MySQL есть уязвимость в обработке многобайтового кода.

4. Условные ошибки

Используя SQL-инъекцию пользователь может легко обойти вход в систему. Приведу пример:
<?php

$sqlStatement = "SELECT * FROM users WHERE username = 'james' AND password = 'secret' OR 1=1;";

?>
Значение 1=1 всегда истинно, таким образом, значение пароля уже не будет иметь значения и злоумышленник сможет войти в систему.

Методы предотвращения и защиты от MySQL-инъекций

  • Использование параметризованных запросов
  • Использование хранимых процедур
  • Применение регулярных выражений
  • Использование функций блокировки
  • Отключение сообщений об ошибках
  • Создание менее привилегированного пользователя
  • Ограничения максимального значение

1. Использование параметризованных запросов

Вместо того, чтобы подставлять значения SQL-заявлений напрямую, подставляйте их параметризованные значения, следующим образом:
<?php

$db_connection = new mysqli("localhost", "user", "pass", "db");
$statement = $db_connection->prepare("SELECT * FROM customers WHERE id = ?");
$statement->bind_param("i", $id);
$statement->execute();

?>
"i" - только целые числа (int)
"d" - числа с плавающей запятой (double)
"s" - строки (string)
"b" - отправляется в пакетах (blob)

2. Использование хранимых процедур

Использование хранимых процедур, тоже может помочь снизить риск возникновения атаки. Использование процедур показано на следующем примере:
<?php

$sqlStatement = "
	CREATE PROCEDURE HUGEORDER
	(
		id INT ,
		quantity INT,
		price DECIMAL(6,2)
	)
	BEGIN
		DECLARE discount_percent DECIMAL(6,2);
		DECLARE discounted_price DECIMAL(6,2);
		SET discount_percent  =  10;
		SET discounted_price = price – discount_percent/100*price;
		IF quantity > 500 THEN
			SET discounted_price = discounted_price - 0.25 * quantity;
		END  IF;
		UPDATE fashion_products
		SET product_price = discounted_price WHERE product_id = id;
		Select * from fashion_products;
	END;
";

?>

3. Применение регулярных выражений

Регулярные выражения используются для того, чтобы привести входные данные к одному шаблону. Например, здесь мы проверяем email клиента на валидность и отвергаем возможность для SQL-инъекций.
<?php

if(!preg_match("/^[0-9a-z\_\.\-]+@([\-a-z0-9]+\.)+[a-z]{2,}$/i", $email))
{
        echo 'INVALID Email Address!';
	return;
}

?>
Также вы можете пользоваться встроенными функциями PHP is_array(), is_bool(), is_double(), is_float(), is_int(), is_integer() и другими, для проверки данных пользователя.

4. Использование функции блокировки

Используйте функцию mysql_real_escape_string() для обработки внешних данных. Например:
<?php

$username = mysql_real_escape_string($username, $ dbLink);

?>
Это очень мощная встроенная функция PHP, способная предотвратить SQL-инъекции в большинстве случаев. Вы можете попробовать внедрить SQL-код после использования mysql_real_escape_string() и тестировать на уязвимости. Эта функция отвергает множество умных методов атак, используемых злоумышленниками.

5. Отключение сообщений об ошибках

Прежде всего избегайте встроенной MySQL функции mysql_error(). Умный взломщик может угадать некоторые параметры базы данных из сообщения об ошибке, а иногда и увидеть параметры соединения. Используйте mysql_error() только на стадии разработки. Но убирайте ее, когда запускаете сайт на сервере.

Также отключите отчеты об ошибках в PHP. Это делается одной строкой:
<?php

// Отключить вывод ошибок
error_reporting(0);

?>
А лучше создайте собственное сообщение об ошибке.
<?php

if(!mysql_query($statement))
{
	echo 'Извините, но сервер не доступен!';
}

?>
В результате, пользователь не узнает из сообщения об ошибке никакой важной информации, такой как, имя базы данных, имя таблицы, имя пользователя и других. Тем самым мы усложняем хакеру возможность узнать структуру SQL-запроса, используя различные инъекции.

6. Создайте менее привилегированного пользователя БД

В большинстве случаев, вы заметите, что посетителям не нужно удалять или обновлять информацию. Представим интернет-магазин. Пользователь может запросить данные (SELECT) или оставить заказ (INSERT).

Таким образом, лучше создать несколько различных пользователей. Для администратора предоставить все привилегии, а для обычного пользователя ограниченные. Пример соединения для различных пользователей:
<?php

$visitorDbLink = mysql_connect('host', 'general_user', 'general_user_pass');
$adminDbLink = mysql_connect('host', 'admin_user', 'admin_pass');

?>
Теперь можно использовать $visitorDbLink для регулирования доступа к базе данных для посетителей, и использовать $adminDbLink для доступа в качестве администратора.

7. Установите ограничения на максимальное значение

Если имя пользователя не может быть больше 10 символов, попробуйте использовать "maxlenght" свойство:
<input name="username" type="text" id="username" maxlength="10" />
Однако, это не совсем полезно, так как пользователь может скачать HTML-форму себе на компьютер и создать свою, более удобную форму, которая будет обрабатываться этим же скриптом. Тогда следует дублировать это ограничение на сервере:
<?php

$name = substr($_POST['name'],0,10);

?>
Функция substr() вернет значение name длиною 10 символов, начиная с нулевого.

Ну вот и все, что я хотел рассказать вам о защите от SQL-инъекции и безопасности баз данных. Надеюсь, что вам было интересно. Спасибо, что дочитали.

P.S. В статье Слепая SQL-инъекция я рассмотрел новые способы защиты от разновидности SQL-уязвимостей.
Источник: Learning Is Fun внешняя ссылка
Тэги:  • 
12 комментариев
23 661 просмотр


#1 Кирилл, 7 апр 2011 в 19:37
норм статья, сделал меньше привилегий для бд
#2 freeeeez, 7 апр 2011 в 19:51
DELETE из привилегий уж точно нужно убрать, а остальные по необходимости.
#3 Санвел, 12 апр 2011 в 16:50
Да так себе статья
#4 Руслан, 23 июн 2011 в 18:51
1. Использование параметризованных запросов

А если в параметр с типом string передать sql запрос, он выполнится?
например:
$pass = "secret ' UNION SELECT password FROM users;\";

$statement = $db_connection->prepare("SELECT * FROM users WHERE username = 'james' AND password = ?");
$statement->bind_param("s", $pass);
$statement->execute();
#5 Руслан, 23 июн 2011 в 18:52
ещё одну кавычку срезало экранирующую в $pass
#6 freeeeez, 23 июн 2011 в 18:59
А если в параметр с типом string передать sql запрос, он выполнится?

Очевидно да, ведь secret ' UNION SELECT password FROM users; - это строка. Однако данный способ в основном хорош для int и double.
#7 Андрей, 16 сен 2011 в 00:30
Против всякого рода иньекций используем в БД Views с параметром Temp Table и ни у кого не будет возможности занести заразу , так как такой метод служит только для выдачи инфы , то есть ее прочтения .. Без права изменения и удаления )
#8 freeeeez, 16 сен 2011 в 08:56
Андрей, этот метод действительно хорош, но он не подходит для динамично обновляемых БД.
#9 wew, 26 мар 2012 в 09:32
Готовые ответы Гос экзаменя для ГМУ 2012 http://goc-new.3dn.ru/
#10 Виталий, 9 дек 2016 в 10:43
Отличная статья, спасибо!
#11 СКС, 28 мар 2017 в 18:58
отлично,защитили свой сайт полезная информация
#12 ELForcer, 30 май 2017 в 07:53
Пароли нужно передавать и принимать в хэше, тогда и DROP Table превратится в хэш и с базой ничего не будет.
А лучше заранее проверять в полученной строке на спец слова типа (select, drop, update) и предупреждать юзверя что нельзя юзать спецслова.

Оставить комментарий:

Имя:
Email:
Сайт:
Комментарий:

Допустимые теги: <em> • <strong> • <u> • <sub> • <sup> • <blockquote>

Проверочный код:

Введите проверочный код, для подтверждения, что вы не робот.
P.S. Если вы робот, то, к сожалению, вы
не сможете прочитать символы с картинки.