Почта:vova@vbeg.ru
Скайп:vova-beglecov

SQL инъекции. Проверка, взлом, защита

SQL инъекция - это один из самых доступных способов взлома сайта.
Суть таких инъекций – внедрение в данные (передаваемые через GET, POST запросы или значения Cookie) произвольного SQL кода. Если сайт уязвим и выполняет такие инъекции, то по сути есть возможность творить с БД (чаще всего это MySQL) что угодно.

Как вычислить уязвимость, позволяющую внедрять SQL инъекции?

Глупые огрехи в коде, позволяющие сломать страницу, вычислить довольно легко. Допустим, имеется тестовый сайт http://test.ru. На сайте выводится список новостей, с возможностью детального просмотра. Адрес страницы с детальным описанием новости выглядит так: http://test.ru/?detail=1. Т.е через GET запрос переменная detail передаёт значение 1 (которое является идентификатором записи в таблице новостей).

Изменяем GET запрос на ?detail=1' или ?detail=1". Далее пробуем передавать эти запросы серверу, т.е заходим на http://test.ru/?detail=1' или на http://test.ru/?detail=1".

Если при заходе на данные страницы появляется ошибка, значит сайт уязвим на SQL инъекции.

Пример ошибки, возникающей при проверке уязвимости
SQL инъекции. Внедрение SQL кода.


Возможные SQL инъекции (SQL внедрения)

1) Наиболее простые - сворачивание условия WHERE к истинному результату при любых значениях параметров.
2) Присоединение к запросу результатов другого запроса. Делается это через оператор UNION.
3) Закомментирование части запроса.


Практика. Варианты взлома сайта с уязвимостью на SQL внедрения

Итак, у нас есть уже упоминавшийся сайт http://test.ru. В базе хранится 4 новости, 3 из которых выводятся. Разрешение на публикацию новости зависит от параметра public (если параметр содержит значение 1, то новость публикуется).

Список новостей, разрешённых к публикации
SQL инъекции - типовой пример взлома

При обращении к странице http://test.ru/?detail=4, которая должна выводить четвёртую новость появляется ошибка – новость не найдена. В нашем случае новость существует, но она запрещена к публикации.
SQL инъекции - новость не найдена

Но так как мы уже проверяли сайт на уязвимость и он выдавал ошибку БД, то пробуем перебирать возможные варианты запросов.
В адресной строке плюс (+) выполняет роль пробела, так что не пугайтесь

Тестирую следующие варианты:
http://test.ru/?detail=4+OR+1
http://test.ru/?detail=4+--
http://test.ru/?detail=4+UNION+SELECT+*+FROM+news+WHERE+id=4

В итоге удача улыбнулась и два запроса (первый и третий) вернули нам детальное описание четвёртой новости
SQL инъекции - атака увенчалась успехом


Разбор примера изнутри

За получение детального описания новости отвечает блок кода:
$detail_id=$_GET['detail'];
$zapros="SELECT * FROM `$table_news` WHERE `public`='1' AND `id`=$detail_id ORDER BY `position` DESC";

Мало того, что $detail_id получает значение без какой либо обработки, так ещё и конструкция `id`=$detail_id написана криво, лучше придерживаться `id`='$detail_id' (т.е сравниваемое значение писать в прямых апострофах).

Глядя на запрос, получаемый при обращении к странице через http://test.ru/?detail=4+OR+1
SELECT * FROM `news` WHERE `public`='1' AND `id`=4 OR 1 ORDER BY `position` DESC
становится не совсем ясно, почему отобразилась 4-ая новость. Дело в том, что запрос вернул все записи из таблицы новостей, отсортированные в порядке убывания сверху. И таким образом наша 4-ая новость оказалась самой первой, она же и вывелась как детальная. Т.е просто совпадение.

Разбираем запрос, сформированный при обращении через http://test.ru/?detail=4+UNION+SELECT+*+FROM+news+WHERE+id=4. Тут название таблицы с новостями (в нашем случае это news) бралось логическим перебором.
Итак, выполнился запрос SELECT * FROM `news` WHERE `public`='1' AND `id`=4 UNION SELECT * FROM news WHERE id=4 ORDER BY `position` DESC. К нулевому результату первой части запроса (до UNION) присоединился результат второй части (после UNION), вернувшей детальное описание 4-ой новости.


Защита от SQL инъекций (SQL внедрений)

Защита от взлома сводится к базовому правилу «доверяй, но проверяй». Проверять нужно всё – числа, строки, даты, данные в специальных форматах.

Числа
Для проверки переменной на числовое значение используется функция is_numeric(n);, которая вернёт true, если параметр n - число, и false в противном случае.
Так же можно не проверять значение на число, а вручную переопределить тип. Вот пример, переопределяющий значение $id, полученное от $_GET['id_news'] в значение целочисленного типа (в целое число):
$id=(int)$_GET['id_news'];

Строки
Большинство взломов через SQL происходят по причине нахождения в строках «необезвреженных» кавычек, апострофов и других специальных символов. Для такого обезвреживания нужно использовать функцию addslashes($str);, которая возвращает строку $str с добавленным обратным слешем (\) перед каждым специальным символом. Данный процесс называется экранизацией.
 
$a="пример текста с апострофом ' ";
echo addslashes($a); //будет выведено: пример текста с апострофом \'

Кроме этого существуют две функции, созданные именно для экранизации строк, используемых в SQL выражениях.
Это функция mysql_escape_string($str); и mysql_real_escape_string($str);.
Первая не учитывает кодировку соединения с БД и может быть обойдена, а вот вторая её учитывает и абсолютно безопасна. mysql_real_escape_string($str); возвращает строку $str с добавленным обратным слешем к следующим символам: \x00, \n, \r, \, ', " и \x1a.


Магические кавычки

Магические кавычки – функция автоматической замены кавычки на обратный слеш (\) и кавычку при операциях ввода/вывода. В некоторых конфигурациях PHP этот параметр включён, а в некоторых нет. Для того, что бы избежать двойного экранизирования символов и заэкранизировать данные по-нормальному через mysql_real_escape_string($str);, необходимо убрать автоматические проставленные обратные слеши (если магические кавычки включены).

Как-то раз на Хабре какой-то дебил сетовал, что параметр get_magic_quotes_gpc ушёл в прошлое и сейчас на хостингах он везде отключен. Доверившись таким знаниям, в одном из проектов я решил не делать проверку магических кавечек. В результате меня ждал неприятный сюрприз и понимание, что верить нужно только себе. Поэтому, не доверяйтесь на то, что может быть в теории, а всё-равно делайте проверку.

Проверка включённости магических кавычек для данных получаемых из GET, POST или Куков организуется через функцию get_magic_quotes_gpc(); (возвращает 1 – если магические кавычки включены, 0 – если отключены).

Если магические кавычки включены (т.е обратные слеши добавляются) и такое встречается чаще, то их нужно убрать. Это делается через функцию stripslashes($str); (возвращает строку $str без обратных слешей у кавычек и прямых апострофов).

В заключении привожу код с полной экранизацией строк для записи в БД
if(get_magic_quotes_gpc()==1)
{
	$element_title=stripslashes(trim($_POST["element_title"]));
	$element_text=stripslashes(trim($_POST["element_text"]));
	$element_date=stripslashes(trim($_POST["element_date"]));
}
else
{
	$element_title=trim($_POST["element_title"]);
	$element_text=trim($_POST["element_text"]);
	$element_date=trim($_POST["element_date"]);
}

$element_title=mysql_real_escape_string($element_title);
$element_text=mysql_real_escape_string($element_text);
$element_date=mysql_real_escape_string($element_date);