Три рубежа
Безопасность движка стоит на трёх вопросах по порядку: кого пускать (аутентификация), что ему можно (авторизация), от чьего имени исполнять код (контекст функций). Дыра на любом рубеже обесценивает остальные.
pg_hba.conf: кого и как пускать
Файл pg_hba.conf - первый барьер, ещё до пароля. Каждая строка:
«для такого-то типа подключения, такой-то базы, роли и адреса -
проверять вот таким методом». Строки читаются сверху вниз, побеждает
первая подходящая.
# TYPE DATABASE USER ADDRESS METHOD
host all all 10.0.0.0/8 scram-sha-256
host all all 0.0.0.0/0 trust # ОПАСНО
Метод trust означает «пускать без проверки пароля вообще». Уместен
разве что для локального сокета в одноразовом сэндбоксе. Строка с
trust на широком диапазоне адресов - это открытая дверь: знаешь имя
роли, и ты внутри. Боевой метод - scram-sha-256.
Роли и привилегии
В PostgreSQL пользователь и группа - это одна сущность, роль. Роль может
логиниться (LOGIN), может владеть объектами, может входить в другие
роли. Привилегии выдаются GRANT и наследуются по членству.
Базовая гигиена: приложение не ходит суперпользователем. Под суперюзером
не действуют права на таблицы и не работает row-level security - один
такой коннект, утёкший наружу, открывает всё. Приложению дают роль ровно
с нужными GRANT на нужные таблицы.
SECURITY DEFINER + search_path: канал эскалации
Обычная функция (SECURITY INVOKER) исполняется с правами вызвавшего.
Функция SECURITY DEFINER - с правами владельца. Это нужно, чтобы
дать пользователю выполнить контролируемое действие над данными, к
которым у него прямого доступа нет.
Опасность - в search_path. Если внутри SECURITY-DEFINER-функции,
принадлежащей суперпользователю, есть неквалифицированный вызов
(SELECT ... FROM accounts, а не public.accounts), то злоумышленник
может создать в своей схеме объект с тем же именем и подсунуть его, сдвинув
свой search_path. Функция выполнит подделку с правами владельца - вот и
эскалация.
-- защита: прибить search_path к самой функции
CREATE FUNCTION admin_action() RETURNS void
LANGUAGE sql
SECURITY DEFINER
SET search_path = pg_catalog, public -- фиксируем, не наследуем от вызвавшего
AS $$ ... $$;
Правило для любой SECURITY DEFINER-функции: всегда SET search_path
явно и квалифицируй имена объектов схемой. Без этого «удобная» функция
превращается в лазейку.
Минимальный чеклист
- в
pg_hba.confнетtrustна сетевых адресах, метод -scram-sha-256; - приложение работает не под суперпользователем;
- у каждой
SECURITY DEFINER-функции закреплёнsearch_path; - привилегии выданы по принципу наименьших прав через
GRANT.
Как DROP без бэкапа превращается в катастрофу - backup-pitr; типовые ошибки в самих запросах - anti-patterns.