linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Главы
  • How it worksскоро
  • Уроки
  • База знаний
  • Собеседование
Часть IX — Эксплуатация и анти-паттерны

$ глава 44 · 45 минут

Безопасность движка (опционально)

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

Безопасность движка стоит на трёх рубежах, идущих по порядку: кого вообще пускать, что пущенному можно, и от чьего имени исполняется код. Дыра на любом из них обесценивает остальные два. Разберём каждый и покажем самый коварный путь эскалации - через SECURITY DEFINER и search_path.

44.1 pg_hba.conf: первый барьер до пароля

Прежде чем проверять пароль, сервер решает, рассматривать ли это подключение вообще. Решает по файлу pg_hba.conf (host-based authentication). Каждая строка отвечает: «для такого типа соединения, такой базы, такой роли и такого адреса - проверять вот этим методом».

# TYPE  DATABASE  USER  ADDRESS         METHOD
local   all       all                   peer
host    all       all   10.0.0.0/8      scram-sha-256
host    all       all   0.0.0.0/0       trust          # ОПАСНО

Строки читаются сверху вниз, побеждает первая подходящая. Метод определяет, как проверять личность:

  • scram-sha-256 - проверка пароля по современному протоколу, боевой вариант;
  • peer - доверие на основе имени системного пользователя, для локального сокета;
  • trust - пускать без проверки вообще.

44.1.1 Подводный камень: trust на сетевом адресе

trust означает «пускать кого угодно без пароля». Для локального одноразового сэндбокса это терпимо. Строка с trust на сетевом диапазоне (0.0.0.0/0 или подсеть) - открытая дверь: достаточно знать имя роли, и ты внутри, без всякого пароля.

Это одна из самых частых реальных дыр: trust, поставленный «на время отладки» и забытый. Боевое правило простое - на любом сетевом адресе метод scram-sha-256, trust только на локальном сокете и только если очень надо.

Проверить действующие правила можно прямо из SQL, не лазая в файл:

sql
SELECT type, database, user_name, address, auth_method
FROM pg_hba_file_rules
WHERE auth_method = 'trust';

44.2 Роли и наименьшие привилегии

В PostgreSQL нет отдельно «пользователей» и «групп» - есть роль. Роль может логиниться (LOGIN), владеть объектами, входить в другие роли. Привилегии выдаются через GRANT и наследуются по членству.

Базовая гигиена - приложение не ходит суперпользователем. Причина не только в «вдруг что-то сломает»: под суперпользователем не проверяются права на таблицы и не работает row-level security. Один такой коннект, утёкший наружу, открывает вообще всё, никакие GRANT и политики его не остановят.

Поэтому приложению заводят отдельную роль с правами ровно на нужные таблицы и операции - принцип наименьших привилегий. Суперпользователь остаётся для миграций и администрирования, а не для повседневных запросов приложения.

44.3 SECURITY DEFINER: исполнение с правами владельца

Обычная функция (SECURITY INVOKER, по умолчанию) исполняется с правами того, кто её вызвал. Функция SECURITY DEFINER - с правами её владельца. Это законный и нужный механизм: дать пользователю выполнить контролируемое действие над данными, к которым у него нет прямого доступа.

Пример: пользователь не имеет права писать в таблицу аудита напрямую, но может вызвать SECURITY-DEFINER-функцию, принадлежащую администратору, которая добавит туда строго одну строку нужного формата. Доступ к таблице остаётся только у функции, не у пользователя.

Проблема в том, что вместе с правами владельца функция уносит и контекст вызвавшего - в частности, его search_path.

44.4 Эскалация через search_path

Вот коварная связка. search_path - порядок, в котором сервер ищет схемы для неквалифицированных имён (accounts без явной public.). Его задаёт вызывающий, и он наследуется внутрь SECURITY-DEFINER-функции.

Пусть функция принадлежит суперпользователю и содержит неквалифицированный вызов:

sql
-- уязвимо: accounts без явной схемы
CREATE FUNCTION charge(amount numeric) RETURNS void
LANGUAGE sql SECURITY DEFINER
AS $$ UPDATE accounts SET balance = balance - amount $$;

Злоумышленник создаёт в своей схеме свою таблицу accounts, ставит свою схему первой в search_path и вызывает charge. Функция, исполняясь с правами суперпользователя, обратится к подделке - или, хуже, к функции-подделке с побочным эффектом. Это полноценная эскалация привилегий.

Защита - закрепить search_path на самой функции и квалифицировать имена схемой:

sql
CREATE FUNCTION charge(amount numeric) RETURNS void
LANGUAGE sql SECURITY DEFINER
SET search_path = pg_catalog, public   -- фиксируем, не наследуем
AS $$ UPDATE public.accounts SET balance = balance - amount $$;
$$;

Правило без исключений: у каждой SECURITY DEFINER-функции явный SET search_path, и имена объектов квалифицированы схемой.

44.5 Минимальный чеклист безопасности

Свести три рубежа в проверяемый список:

  • в pg_hba.conf нет trust на сетевых адресах, метод - scram-sha-256;
  • приложение работает не под суперпользователем, права выданы по наименьшим привилегиям через GRANT;
  • у каждой SECURITY DEFINER-функции закреплён search_path и имена квалифицированы схемой;
  • row-level security включена там, где разграничение по строкам - часть модели доступа (и помни, что суперпользователь её обходит).

Этот список не заменяет полноценный аудит, но закрывает самые частые реальные дыры. Подробная справка - engine-security. А почему DROP без бэкапа - отдельная катастрофа, мы видели в главе про PITR.

Уроки в sandbox

lab-44.1. search_path и SECURITY DEFINER: воспроизведи подмену

Воспроизведём, как неквалифицированное имя в функции даёт подменить объект через search_path, и закроем дыру закреплением search_path. Полную демонстрацию с pg_hba и trust оставим топологии с рестартом; здесь - часть, которая работает на одном сервере. Сначала предскажешь, какую таблицу увидит функция, потом проверишь.

  1. Создай «настоящую» таблицу и функцию без закреплённого search_path: 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 $$;.

  2. Вызови функцию: SELECT read_secret(); - вернёт 'real'. Запомни.

  3. Сыграй подмену: CREATE SCHEMA evil; CREATE TABLE evil.secret(v text); INSERT INTO evil.secret VALUES ('fake'); SET search_path = evil, public;. Предскажи, что вернёт read_secret() теперь.

  4. Проверь: SELECT read_secret(); - функция без закреплённого search_path возьмёт evil.secret и вернёт 'fake'. Имя secret разрешилось в чужую схему.

  5. Закрой дыру: пересоздай функцию защищённо - 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 $$;. Снова SELECT read_secret(); при том же search_path = evil, public - теперь стабильно 'real'.

sandbox с автопроверкой - открыть в песочнице

Резюме

  • pg_hba.conf - первый барьер до пароля: строки читаются сверху вниз, метод определяет, как проверять личность (scram-sha-256, peer, trust).
  • trust пускает без пароля; на сетевом адресе это открытая дверь и одна из самых частых реальных дыр - на сети только scram-sha-256.
  • В PostgreSQL пользователь и группа - одна сущность, роль; привилегии выдаются GRANT и наследуются по членству.
  • Приложение не должно ходить суперпользователем: под ним не проверяются права на таблицы и не работает row-level security.
  • SECURITY DEFINER исполняет функцию с правами владельца - законный способ дать контролируемый доступ к закрытым данным.
  • Незакреплённый search_path в SECURITY DEFINER-функции - канал эскалации: подделка объекта в своей схеме выполнится с правами владельца.
  • Защита - SET search_path на самой функции плюс квалификация имён схемой; это правило без исключений.

Контрольные вопросы

  1. Почему строка с trust в pg_hba.conf на сетевом адресе так опасна?

    Показать ответ

    Метод trust означает, что сервер пускает подключение вообще без проверки пароля. На локальном одноразовом сокете это терпимо, но на сетевом диапазоне (например 0.0.0.0/0) это значит: кто угодно, кто знает имя роли и может дотянуться по сети, заходит в базу без всякой аутентификации. Это частая реальная дыра - trust, поставленный «на время отладки» и забытый. На сетевых адресах метод должен быть scram-sha-256.

  2. Почему приложению нельзя ходить в базу суперпользователем?

    Показать ответ

    Под суперпользователем PostgreSQL не проверяет права на таблицы и не применяет row-level security - суперюзер обходит и то, и другое. Значит, ни GRANT, ни политики RLS его не ограничат. Если такой коннект (или его учётка) утечёт, открыт весь кластер. Приложению заводят отдельную роль с правами ровно на нужные таблицы и операции по принципу наименьших привилегий, а суперпользователя оставляют для миграций и администрирования.

  3. Что делает SECURITY DEFINER и зачем он нужен?

    Показать ответ

    Обычная функция исполняется с правами вызвавшего (SECURITY INVOKER). SECURITY DEFINER исполняет функцию с правами её владельца. Это нужно, чтобы дать пользователю выполнить контролируемое действие над данными, к которым прямого доступа у него нет: например, добавить строго одну строку в таблицу аудита, к которой сам он писать не может. Доступ остаётся у функции, а не у пользователя.

  4. Как незакреплённый search_path превращает SECURITY DEFINER в эскалацию привилегий?

    Показать ответ

    search_path задаёт вызывающий, и он наследуется внутрь SECURITY-DEFINER-функции. Если функция принадлежит суперпользователю и содержит неквалифицированное имя (accounts без схемы), злоумышленник создаёт в своей схеме объект accounts, ставит свою схему первой в search_path и вызывает функцию. Та, исполняясь с правами владельца, обращается к подделке - и выполняет её с привилегиями суперпользователя. Это и есть эскалация.

  5. Как защитить SECURITY DEFINER-функцию от подмены через search_path?

    Показать ответ

    Двумя мерами вместе. Первая - закрепить search_path на самой функции через SET search_path = pg_catalog, public, чтобы она не наследовала путь вызывающего. Вторая - квалифицировать все имена объектов схемой (public.accounts, а не accounts). Тогда подсунуть объект из чужой схемы не выйдет. Это правило применяют к каждой SECURITY DEFINER-функции без исключений.

← Предыдущая43-anti-patternsСледующая →45-optimization-checklist
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки