Укол слону. Руководство по реализации SQL-Injection в PostgreSQLSpyder (spyder@antichat.net) Говоря о базах данных, человек, работающий в Вебе, скорее всего вспомнит MySQL, а программист, создающий БД для крупных торговых компаний, – Oracle. Тем не менее, PostgreSQL сейчас одна из самых мощных реляционных СУБД наряду с Oracle и Sybase и, стоит заметить, бесплатная. Все больше крупных интернет-порталов строятся именно на ней. Ты узнаешь, как реализуются инъекции в этой СУБД, а также результаты небольшого сравнения с MySQL. Поехали! Union, все гениальное - простоИтак, сразу к делу. Рассмотрим инъекцию вида
Если мы не знаем количество столбцов, то подбираем так же, как и в MySQL:
– либо, если у нас есть вывод ошибок, следующим образом:
В результате увидим ошибку:
Отнимаем от этого числа единицу и получаем количество столбцов в запросе. В нашем случае это будет 4. Составляем еще один запрос: Важное отличие MySQL от большинства других СУБД – в том, что она игнорирует конфликт в типах колонок при UNION. В нашем случае поля имеют тип:
Попытка составить, например, такой запрос:
привела бы к следующей ошибке –
Как правило, нам требуется вывод в столбцах типа text или char. Узнаем о себеПрежде всего, узнаем, кто мы такие (т.е. свои права и обязанности), выполнив запрос:
А также получим полное инфо о сервере:
Здесь стоит разобрать запрос по частям. Во-первых, в нем используются специфические функции.
Во-вторых, для объединения используются символы пайпов «||», аналог функции concat() в MySQL. И, в-третьих, разделителем выступает символ двоеточия «:», обрамленный кавычками. Запрос возвратит результат:
Рассмотрим случай, когда уязвимое приложение написано на PHP, и в скрипте используется функция addslashes(), либо в файле php.ini включена директива magic_quotes_gpc. 1. chr() Функция chr() получает один числовой аргумент «n» типа integer и возвращает символ с ASCII-кодом, равным «n». Узнаем чар-код символа «:» и составляем запрос:
Это не очень удобно, так как иногда в кавычки нужно обрамлять довольно длинную строку. Тут существует более удобный способ, описанный ниже. 2. $text$ Два знака доллара говорят PostgreSQL о том, что далее в запросе следует строка. И это самый лучший способ обхода экранирования кавычек. Наш запрос будет выглядеть так: По правде говоря, слово text между знаками доллара можно опустить :). Системная информацияКак и в других СУБД, в PosgreSQL существуют стандартные системные таблицы. К некоторым имеют доступ все, а к некоторым только супер-юзер. 1. pg_user Таблица, доступная всем пользователям. Польза хакеру от нее небольшая, но все же она есть. Интересные поля:
Чтобы узнать больше информации о нашем пользователе, составляем такой запрос: В итоге, получим результат вида
В предыдущем запросе была использована функция cast(), она, так же, как и в MySQL, преобразует типы данных. В нашем случае происходит преобразование int->text и boolean->text. 2. pg_shadow Ты сейчас, скорее всего, вспомнил файл /etc/shadow, в котором находятся пароли пользователей в большинстве *nix-систем. И не зря! Таблица pg_shadow, в отличие от pg_user, хранит в себе еще и пароли пользователей. Попробуем узнать пароль:
К сожалению, доступ к таблице pg_shadow есть только у юзера с правами usesuper. 3. pg_language Это таблица, в которой содержится информация об установленных процедурных языках. Подробнее – чуть позже, а пока приведу ее неполную структуру:
Встречайте... Information_schema!Радоваться или плакать, но в PostgreSQL существует полюбившаяся нам в MySQL > 5.0 база information_schema, содержащая в себе информацию обо всех таблицах и колонках, доступных текущему пользователю. Структура ее практически идентична. Стоит заметить, что оператор limit имеет в PostgreSQL другой вид и состоит из двух частей:
Итак, мы нашли имя таблицы, – пусть это будет users. Узнаем имена полей запросом: Ну а дальше выводим, так же, как и в MySQL (только не забывай про типы и limit offset). Разделяй и властвуйДо этого момента мы рассматривали классическую инъекцию с использованием оператора UNION. Сейчас я покажу тебе более интересные способы. Большинство из ниже описанного будет работать, если у текущего пользователя есть права usesuper. Замечу, что такое тут встречается намного чаще, чем в MySQL. Один из главных плюсов инъекции в PostgreSQL - возможность разделений запросов символов точки с запятой ';'.В нашем примере это будет выглядеть так: Минус такого способа – мы не видим вывода второго запроса, но и это нам не помеха. А поможет нам здесь знакомое преобразование типов. Попробуем преобразовать тип text в тип boolean и посмотрим, что из этого выйдет: В результате запрос вернет ошибку:
Однако этот способ не будет работать, если поле имеет тип name. К примеру, такой запрос:
возвратит ошибку –
Но и это нам не помеха. Мы знаем, что ошибку выдает преобразование типа text в тип boolean. А что нам мешает сделать двойное преобразование? Первой функцией мы переводим name в text, а второй text в boolean. В результате получаем ошибку: При использовании этого способа есть одна особенность. Мы не можем выводить интересующую нас запись, используя limit offset. Для этого нам потребуется составить конструкцию where columnname not in (). Предположим, что предыдущий запрос вернул запись admin. Тогда составим такой: Таким образом, мы можем перебрать все содержимое таблицы. Тестируя этот метод на различных сайтах, я столкнулся с проблемой, которую можно объяснить различием в версиях. Заключается она в преобразовании типа boolean в любой другой тип. В официальной документации сказано: Несмотря на это, у половины тестируемых сайтов, запрос вида: не приводил к ошибке и возвращал вполне определенное значение. С другой стороны, является ли это большим минусом? Скорее всего, нет. Так как большинство потенциально интересных полей имеют тип name,text,char, который благополучно приводится к другим типам и вызывает нужную нам ошибку. Плюсы же способа очевидны. Мы не используем union, нам не нужно подбирать количество колонок, а также искать правильный ее тип. Способ будет работать с любой слепой инъекцией. Сложную конструкцию функций cast() можно заменить на более простую, с помощью двух двоеточий '::'. Следующий запрос вернет нам название базы данных и имя таблицы: Многострочные запросыИногда SQL-запрос может состоять из нескольких строк. Пример: В этом случае использование символов «--» нам не поможет, так как закомментируется только текущая строка. Чтобы реализовать SQL-Injection, нужно составить синтаксически правильный запрос. В данном случае он будет выглядеть так: Если мы не знаем имя таблицы, из которой извлекаются данные, можно составить универсальный запрос для любого случая: Все прелести usesuperА теперь рассмотрим случай, когда запрос возвращает значение true. Здесь у нас необъятное поле для действий, начиная с вывода всей информации из таблицы и заканчивая выполнением системных команд на сервере. Сейчас ты поймешь, что PostgreSQL не только мощная, но и в неумелых руках очень опасная штука. Limit? No LimitЗачем использовать limit или not in (), когда мы можем вывести сразу все записи. Для этого нам нужно, чтобы был включен язык plpgsql, либо, если имеем права usesuper, создадим его самостоятельно: А вот и сама функция:
Функция получает 6 параметров, имя БД, название таблицы и 4 колонки. Пример использования: А вот и результат: Читаем файлыЧтобы прочитать файл, нам нужно выполнить несколько действий. Для начала создадим таблицу с одним полем типа text:
После этого занесем содержимое файла /etc/hosts в таблицу:
– и прочитаем его уже известным нам способом.
Каждая новая строка файла записывается в новое поле таблицы. Предыдущий запрос выведет лишь первую строку. Для вывода всего файла построчно используй либо where file not in(), либо, если ты используешь конструкцию UNION - limit 1 offset n. Создаем файлыИмея права суперюзера, мы можем создать произвольный файл на сервере. Для этого выполняем запрос: И видим результат: К счастью для нас, PostgreSQL создает файл с правами чтения для всех, что позволяет, при наличии локального инклуда, записать код в файл и выполнить его. Помимо этого, существуют стандартные функции администрирования сервера:
Однако реальной пользы от этих функций мало, так как они работают только в директории, указанной в $PGDATA, и выйти из нее не получится.
Выполнение произвольного кодаPostgreSQL является очень мощной базой данных с поддержкой многих процедурных языков. В стандартный дистрибутив сейчас входят следующие:
Помимо стандартных языков, имеются сторонние реализации, такие как plPHP, plRuby и plJava. Имея права usesuper, мы можем создать функцию на любом из этих языков. Прежде, чем начать, вернемся к таблице pg_language. Для этого выполним запрос: Здесь мы не используем объединение строк с помощью пайпов, а просто вводим имена колонок через запятую, в результате видим ошибку: Теперь мы знаем, что язык plperl является процедурным, а также безопасным. Что же в нем безопасного, спросишь ты. А я тебе покажу на примере. Попробуем создать простенькую функцию, которая будет принимать один параметр типа text и выводить символы в обратном порядке. Такой запрос будет иметь вид: Теперь опробуем ее на деле: В ошибке видим следующее:
Следовательно, все работает, как надо. Но нам этого мало, нужно получить выполнение системных команд. Вот именно здесь и играет свою роль lanpltrusted. Попытки создать функцию с использованием system(), print `` и open() с пайпами возвращают соответствующие ошибки:
Постгрес не дает нам создать потенциально опасные функции, но и это можно обойти. Для начала создадим новый язык по шаблону языка Perl. Список шаблонов находится в таблице pg_pltemplate. Чтобы создать новый язык, пишем:
В таблице pg_language появится новый язык plperlu, но поле lanpltrusted уже будет false, символ «u» как раз и означает не доверенный (Untrusted). Теперь нам остается создать функцию, которая будет выполнять системную команду на сервере и выводить ее результат:
Выполняем:
В ответ получаем:
А дальше уже – кто на что горазд. Можно поискать папки на запись, можно сразу залить на сервер бекконект, – в таком случае у нас будут права postgres. Также приведу примеры системных функций на других языках:
dblink() и trust аутентификацияВ завершение моей статьи хочу рассказать тебе о редко встречающейся, но крайне полезной функции под названием dblink(). Создана она была для подключения к другой базе данных непосредственно в самом SQL-запросе. Ее использование выглядит так:
А теперь я расскажу, что же такое локальная трастовая аутентификация. По умолчанию PostgreSQL позволяет локально подключаться любому пользователю даже без пароля. Что это нам дает? При наличии dblink() мы можем выполнять запросы непосредственно от имени супер пользователя. Вот пример подобного запроса:
Ну а что делать, когда имеешь права суперюзера, ты уже знаешь. OutroВ статье я попытался раскрыть наиболее важные аспекты проведения инъекций в PostgreSQL, затронув их ключевые особенности. Всю документацию ты можешь получить на официальном сайте postgresql.org, либо... пиши мне на e-mail :). WARNINGВнимание! Информация представлена исключительно с целью ознакомления! Ни автор, ни редакция за твои действия ответственности не несут! DVDНа диске ты сможешь найти видео, демонстрирующее проведение инъекции, от определения принтабельного поля до получения шелла на сервере. |