lesson ── postgres-labs ── ~20 мин ── 3 шагов
SECURITY DEFINER-функция исполняется с правами владельца. Если внутри неё есть неквалифицированное имя таблицы, а search_path не закреплён, чужой объект можно подсунуть через search_path - и функция выполнит подделку с правами владельца. Ты воспроизведёшь эту подмену и закроешь дыру. Автопроверка здесь подключается заново и видит search_path, заданный для роли, - именно так подмена и проявляется на практике. Сначала предсказывай, потом проверяй.
интерактивный sandbox
Поднимется контейнер postgreslab/postgres-base с PostgreSQL 17 и psql. В браузере откроется терминал, база lab уже настроена. Каждый шаг проверяется автоматически. Сеть air-gapped, наружу контейнер не ходит.
stack ── PostgreSQL 17 · psql · 1 GB RAM · air-gapped · самоуничтожается через 45 мин простоя
Заведи «настоящую» таблицу и функцию, читающую её по неквалифицированному имени:
CREATE TABLE public.secret (v text);
INSERT INTO public.secret VALUES ('real');CREATE FUNCTION read_secret() RETURNS text
LANGUAGE sql SECURITY DEFINER
AS $$ SELECT v FROM secret LIMIT 1 $$;
Предскажи, что вернёт SELECT read_secret(); сейчас.
✓ Функция возвращает 'real' - читает public.secret. Пока всё честно.
Создай свою схему с таблицей того же имени и поставь её первой в search_path роли:
CREATE SCHEMA evil;
CREATE TABLE evil.secret (v text);
INSERT INTO evil.secret VALUES ('fake');ALTER ROLE student SET search_path = evil, public;
search_path роли подхватывают новые подключения. Предскажи: что
вернёт read_secret() в новом соединении - 'real' или 'fake'? Функция
без закреплённого search_path возьмёт первое подходящее secret.
ALTER ROLE влияет на новые сессии; автопроверка как раз открывает новое соединение.
✓ Функция вернула 'fake' - имя secret разрешилось в evil.secret. Это эскалация.
Пересоздай функцию защищённо: закрепи search_path и квалифицируй имя схемой.
CREATE OR REPLACE FUNCTION read_secret() RETURNS text
LANGUAGE sql SECURITY DEFINER
SET search_path = pg_catalog, public
AS $$ SELECT v FROM public.secret LIMIT 1 $$;
search_path роли всё ещё evil, public, но функция его игнорирует.
Предскажи: что вернёт read_secret() теперь? Когда закончишь, верни
путь: ALTER ROLE student RESET search_path;.
✓ Снова 'real' при том же search_path роли - подмена больше не проходит.
SECURITY DEFINER исполняет функцию с правами владельца и наследует search_path вызывающего. Неквалифицированное имя внутри такой функции - канал подмены: чужой объект из первой схемы search_path подставится и выполнится с правами владельца. Защита: SET search_path на функции плюс квалификация имён схемой.
команды
CREATE FUNCTION f() ... SECURITY DEFINER SET search_path = pg_catalog, public ...закрепить search_path на функцииALTER ROLE r SET search_path = a, b;search_path роли (подхватывается новыми сессиями)SELECT * FROM pg_hba_file_rules WHERE auth_method='trust';найти опасные trust-правилаконцепции