Что такое SQL-инъекция и как с ее помощью взламывают сайты

17 Июня 2015
Если облака для вас
не просто теория
Широкий спектр услуг
по выделенным северам
и мультиклауд-решениям
Конфигурация VPS и бесплатный тест уже через 2 минуты
Организация вашей IT-инфраструктуры на основе мультиклауд-решения
Создание динамического сайта всегда подразумевает использование того или иного сервера баз данных. В подавляющем большинстве случаев используется сервер баз данных MySQL. Сам по себе MySQL довольно надежен, стабилен и быстр в плане работы (хотя тут многое зависит от типа выполняемой операции – вставка, выборка, удаление, изменение данных или что-то еще). Самые основные проблемы обычно связаны с приемом и обработкой запросов. Если сделать это некорректно, то ваш сайт могут взломать.

Как же именно происходит взлом посредством SQL-инъекции? Для начала следует понять, что данные для запроса очень часто принимаются от пользователя, к примеру, при заполнении формы или ее отдельных полей (используется метод POST). Также данные могут приходить из GET-запроса, текст которого формируется и отображается в строке URL-адреса. Приведем небольшой пример. Предположим, что ссылка на страницу со статьей имеет следующий вид – site.ru/article.php?id=1. Файл article.php – это общий шаблон вывода статьи. После знака вопроса указан параметр id с определенным значением. В зависимости от этого значения происходит заполнение шаблона тем или иным контентом. В данном случае страница заполняется статьей с id = 1. Сама статья берется из базы данных и ищется там как раз по этому идентификатору. Если в URL-адресе изменить цифру 1 на 2, то нам придет вторая статья, если на 3, то третья и т.д.

В PHP-коде страницы сначала происходит получение идентификатора и запись его в какую-либо переменную. Предположим, что переменная будет называться $id. Далее формируется запрос к базе данных. Выглядеть он может приблизительно так – «SELECT * FROM articles WHERE id = $id» (выбирается все из таблицы «articles», где данные в столбце id равны переменной $id). Злоумышленник может руками (в нашем случае через поле ввода URL) изменить значение id с цифрового на любое другое. К примеру, вместо цифры 1 он пропишет там следующее – «1 UNION SELECT * FROM admin». Если подставить эту строку в наш верхний запрос, то получится следующее - «SELECT * FROM articles WHERE id = 1 UNION SELECT * FROM admin». Расшифровка запроса - выбрать все данные из таблицы «articles», где id = 1, а также выбрать все данные из таблицы «admin». Предполагается, что в таблице «admin» хранятся учетные записи администраторов (в том числе логины и пароли). Как правило, перед выполнением целевого запроса злоумышленник узнает структуру базы, имена таблиц, полей и типы данных, которые хранятся в этих полях.

В описанном выше примере использовался оператор UNION, который объединяет два запроса. Кроме него может использоваться такой прием, как внедрение в строковые параметры. Допустим, изначальный запрос к базе выглядит так - «SELECT * FROM articles WHERE title LIKE (‘%$title%’)». Если вместо title поставить это – «’ and id = ‘1», то мы получим вот что - «SELECT * FROM articles WHERE title LIKE (‘%’ and id = ‘1%’)». В данном случае к запросу опять же добавляются дополнительные слова за счет манипуляций с открывающимися и закрывающимися кавычками.

Еще один способ внедрения SQL-инъекции – расщепление запроса. Подставив вместо идентификатора id следующую строку – «1; SELECT * FROM admin», мы получим следующее - «SELECT * FROM articles WHERE id = 1; SELECT * FROM admin». По сути, это тоже два запроса, которые выполнятся за один раз.

Возникает вопрос – как же защититься от инъекций?

Шаг первый – тщательная проверка данных, присланных пользователем. С числовыми данными дела обстоят очень просто. Во-первых, можно использовать PHP-функцию is_numeric, которая проверит, является ли переменная числом. Кроме того, можно выполнить принудительное преобразование переменной в число. Пример – $x = abs((int)$x). В данном случае мы приводим $x к целому положительному числу. Если злоумышленник подсунет в запрос строку, то она все равно станет числом. Со строковыми данными все будет немного по-другому. Здесь нам нужно удалить из запроса кавычки, начальные и конечные пробелы, а также теги (конечно, если все это присутствует в строке). Пример - $data = mysqli_real_escape_string($link,trim(strip_tags($data))). В данном случае строку $data мы сначала пропускаем через strip_tags (удаляем теги), затем через trim (пробелы), а затем – через mysqli_real_escape_string (экранируем специальные символы, в том числе и кавычки). $link – это ссылка на соединение с базой данных.

Шаг второй – грамотная обработка ошибок. При подставлении в GET-запрос неверного параметра на странице может высветиться очень красноречивое для злоумышленника сообщение с информацией о структуре базы. Дабы такого не происходило, следует обработать возможные ошибки. К примеру, если статьи с переданным в GET-запросе id не существует, то можно вывести соответствующее сообщение. Либо просто перенаправить пользователя на другую страницу. Вариантов здесь масса.

Шаг третий – использование подготавливаемых запросов. Расширение mysqli в PHP позволяет использовать так называемые prepared statements вместо обычных запросов. Их суть заключается в том, что сначала запрос подготавливается для приема параметров, затем происходит добавление параметров с указанием их типов (строка, число и т.д.), и только потом – непосредственное выполнение. Приведем пример

$query = "INSERT INTO articles(title,text,date_time) VALUES(?,?,?)";

if(!$stmt = mysqli_prepare($link,$query))

return false;

mysqli_stmt_bind_param($stmt, "ssi", $title, $text, $date_time);

mysqli_stmt_execute($stmt);

mysqli_stmt_close($stmt);


 В первой строке мы говорим, что будет происходить вставка трех параметров в соответствующие столбцы таблицы «articles». Однако не указываем сами параметры. Вместо них проставлены лишь знаки вопроса. Функция mysqli_prepare подготавливает данный запрос для последующего исполнения. Функция mysqli_stmt_bind_param позволяет забиндить конкретные параметры на место знаков вопроса. Строка "ssi" является перечислением первых букв типов параметров (string, string, integer). В конце происходит выполнение запроса. В данном случае злоумышленник попросту не сможет расширить текст запроса, подставить в него какие-то свои «плохие» данные, так как заранее известно количество принимаемых параметров и их типы.