PostgreSQL / EDB

Monitoring Bezpieczeństwa PostgreSQL — Praktyczny Przewodnik

2026-03-09
Podziel się

Spis treści:

Wprowadzenie

PostgreSQL to jeden z najczęściej wybieranych systemów zarządzania bazami danych w środowiskach produkcyjnych — od startupów po instytucje finansowe podlegające rygorystycznym regulacjom. Jego otwartość, rozszerzalność i bogactwo funkcji stanowią jednocześnie siłę ale i potencjalną powierzchnię ataku. Domyślna konfiguracja PostgreSQL priorytetyzuje kompatybilność i łatwość użycia, nie zaś maksymalne bezpieczeństwo, co sprawia, że każde wdrożenie produkcyjne wymaga świadomego hardeningu i — co równie istotne — ciągłego monitoringu.

Bezpieczeństwo bazy danych to nie jednorazowe działanie, lecz proces. Standardy branżowe takie jak CIS PostgreSQL 16 Benchmark, NIST 800-53, PCI-DSS 4.x, ISO 27001 oraz OWASP PostgreSQL Security Cheat Sheet definiują konkretne kontrole, które organizacja powinna wdrożyć i weryfikować cyklicznie. Każdy z tych standardów kładzie nacisk nie tylko na konfigurację, ale przede wszystkim na monitorowanie — bo nawet najlepsza konfiguracja traci wartość, jeśli nikt nie pilnuje, czy nie została zmieniona.

Niniejszy post stanowi praktyczny przewodnik po monitoringu bezpieczeństwa PostgreSQL. Został podzielony na pięć rozdziałów:

  • Wprowadzenie — kontekst i uzasadnienie potrzeby monitoringu.
  • Miejsce Monitoringu — gdzie monitoring bezpieczeństwa wpisuje się w architekturę zabezpieczeń.
  • Monitoring dryfu konfiguracyjnego i ataków w czasie rzeczywistym — dwa kluczowe tryby pracy.
  • Lista kontrolna: katalogi systemowe do monitorowania — 21 widoków i tabel pg_catalog z mapowaniem na standardy.
  • Lista kontrolna: wykrywanie zaawansowanych śladów intruzji — ukryte bazy, obiekty, kolumny, constrainty i zależności.

Miejsce Monitoringu w architekturze bezpieczeństwa

Monitoring bezpieczeństwa bazy danych nie zastępuje ani hardeningu, ani kontroli dostępu — jest ich nieodzownym uzupełnieniem. W modelu Defense in Depth monitoring pełni rolę czujnika umieszczonego za każdą warstwą obrony: jeśli mechanizm prewencyjny zawiedzie, to właśnie monitoring wykrywa anomalię i uruchamia reakcję.

Monitoring w procesie zabezpieczania Postgresa pełni dwie role, które łatwo pomylić:

  • Weryfikacja założeń bezpieczeństwa – czy to, co skonfigurowaliśmy (rola aplikacji, TLS, reguły pg_hba.conf, ograniczenia), faktycznie obowiązuje w produkcji i nie uległo „cichej” zmianie.
  • Wczesna detekcja nadużyć i awarii bezpieczeństwa – czy dzieje się coś, co przypomina atak lub błąd o podobnych skutkach (eskalacja uprawnień, eksfiltracja danych, bruteforce, DoS przez połączenia/locki).

Żeby to było wykonalne, monitoring Postgresa powinien korzystać z trzech warstw telemetrii:

  • Warstwa hosta / platformy — CPU/RAM/IO, proces postgres, porty, zasoby kontenera/VM, błędy dysku i sieci wykrywanie nieszyfrowanych połączeń (widok pg_stat_ssl), monitorowanie adresów klientów (pg_stat_activity, pg_stat_replication) oraz reguł uwierzytelniania (pg_hba_file_rules). Narzędzia typu IDS/IPS uzupełniają ten obraz o ruch spoza samej bazy.
  • Warstwa instancji PostgreSQL— ciągła weryfikacja ról, uprawnień, rozszerzeń, funkcji SECURITY DEFINER i parametrów runtime. Obejmuje widoki takie jak pg_authid, pg_roles, pg_extension, pg_proc czy pg_settings. To rozszerza obraz uzyskiwany z logów serwera i parametrów uruchomieniowych. To tutaj wykrywany jest dryf konfiguracyjny — niezamierzone lub złośliwe zmiany ustawień.
  • Warstwa obiektów (poziom schematu) — Ten poziom obejmuje analizę struktury tabel, schematów, kolumn i constraintów. Atakujący, którzy uzyskali nieautoryzowany dostęp, często ukrywają swoją aktywność „w planie widoku” — tworzą obiekty w niestandardowych schematach, dodają ukryte kolumny lub podpinają złośliwy kod pod ograniczenia typu CHECK. Dlatego regularne audytowanie definicji obiektów jest równie istotne jak monitoring sesji czy zapytań.

Podstawowym źródłem informacji są widoki z rodziny pg_stat_*, katalog systemowy pg_catalog oraz wybrane widoki systemowe:

  • sesje i połączenia: pg_stat_activity, pg_stat_ssl
  • blokady: pg_locks
  • parametry konfiguracyjne i ich źródło: pg_settings
  • role i uprawnienia: pg_roles oraz katalogi obiektów (pg_class, pg_namespace, listy ACL)
  • replikacja: pg_stat_replication (jeśli dotyczy)
  • statystyki zapytań: pg_stat_statements (jeśli rozszerzenie jest włączone)

Dlaczego ten ostatni poziom ma tak duże znaczenie

Monitoring zawartości wewnętrznych tabel systemowych jest wyjątkowo praktyczny, ponieważ dostarcza sygnałów bezpośrednio u źródła. Zamiast wnioskować o problemach na podstawie objawów, widzisz wprost: kto, skąd, czym i na czym pracuje — oraz co zmieniło stan systemu. To zarazem najbardziej zaawansowany i najrzadziej wdrażany poziom monitoringu w typowych środowiskach produkcyjnych.

Narzędzia audytu i centralizacja logów

Narzędzia takie jak pgAudit rozszerzają wbudowane logowanie PostgreSQL o granularny audyt operacji SELECT, INSERT, UPDATE, DELETE oraz zmian DDL. W środowiskach hybrydowych scentralizowane platformy — np. Percona PMM, ClusterControl czy Prometheus + Grafana — agregują logi i metryki ze wszystkich węzłów, zapewniając jednolity widok stanu bezpieczeństwa całego klastra. Wartościowym uzupełnieniem jest PGDSAT (PostgreSQL Database Security Assessment Tool), który automatyzuje ocenę ponad 70 kontroli bezpieczeństwa, obejmując wszystkie rekomendacje CIS Benchmark.

Ciągłość monitoringu jako zasada nadrzędna

Monitoring musi być ciągły i zautomatyzowany — to warunek konieczny jego skuteczności. Jednorazowy audyt daje wyłącznie obraz punktowy; dopiero cykliczne porównywanie snapshotów katalogu systemowego z konfiguracją bazową (baseline) pozwala wykryć zmiany, które w izolacji wyglądają jak normalne zachowanie PostgreSQL, a w rzeczywistości mogą być śladem intruzji.

Monitoring dryfu konfiguracyjnego i ataków w czasie rzeczywistym

Monitoring bezpieczeństwa PostgreSQL operuje w dwóch komplementarnych trybach: wykrywanie dryfu konfiguracyjnego (zmian stanu systemu) oraz detekcja aktywnych zagrożeń (zdarzeń w czasie rzeczywistym). Oba tryby są niezbędne — żaden nie zastąpi drugiego.

Dryf konfiguracyjny

Dryf konfiguracyjny występuje wtedy, gdy rzeczywisty stan bazy danych odbiega od stanu zdefiniowanego w polityce bezpieczeństwa, skryptach IaC lub zapisanym baseline. Przyczyny bywają prozaiczne — ręczny hotfix wprowadzony pod presją czasu, zapomniane uprawnienia z okna serwisowego, brak wersjonowania zmian DDL — jednak konsekwencje mogą być poważne: luki w compliance, eskalacja uprawnień, wycieki danych.

Trzy filary skutecznej detekcji dryfu:

  • Snapshot & Diff — codzienne snapshotowanie kluczowych katalogów systemowych (pg_roles, pg_proc, pg_extension, pg_settings, pg_hba_file_rules, pg_namespace, pg_class, pg_attribute) i porównywanie z poprzednim stanem. Każda nieoczekiwana różnica wymaga uruchomienia alarmu i wyjaśnienia.
  • Event Triggers — wbudowane wyzwalacze DDL PostgreSQL przechwytują zdarzenia CREATE, ALTER, DROP w momencie ich wystąpienia, umożliwiając logowanie zmian do dedykowanej tabeli audytowej.
  • IaC Drift Detection — w środowiskach zarządzanych przez narzędzia Infrastructure as Code tj. Terraform, Pulumi lub CloudFormation dodatkową warstwą jest porównanie stanu deklarowanego w kodzie ze stanem faktycznym instancji, np. za pomocą PGCLI lub Liquibase Drift Reports.

Trzy klasy sygnałów w praktyce:

  • Parametry instancji — cykliczny odczyt i porównanie kluczowych wartości z pg_settings, z uwzględnieniem źródła parametru (plik konfiguracyjny, ALTER SYSTEM, ustawienie runtime). Pozwala to wykryć m.in. rozluźnienie logowania, wyłączenie wymuszania TLS czy podniesienie limitów zasobów.
  • Reguły dostępu i tożsamości — obserwacja zmian w plikach konfiguracyjnych, sygnałów reload/restart w logach oraz metadanych ról: nowe role, zmiany członkostw w grupach, przyrosty uprawnień.
  • Zmiany w obiektach bazy — wykrywanie nietypowych operacji DDL/DCL: tworzenia rozszerzeń, funkcji, schematów oraz zmian właścicieli obiektów i list ACL. W wielu udokumentowanych incydentach to właśnie pozornie „niewinny” obiekt — np. funkcja z ukrytą logiką — stanowił wektor eskalacji uprawnień.
Zasada kluczowa: monitoring nie powinien zgłaszać wszystkiego — tylko to, co narusza ustalony baseline. Dryf analizowany bez punktu odniesienia zawsze kończy się szumem alertów, który zespół zaczyna ignorować.

Detekcja zagrożeń w czasie rzeczywistym

Drugi tryb monitoringu koncentruje się na obserwacji aktywnych sesji i wzorców zachowań wskazujących na trwający atak. W przeciwieństwie do analizy dryfu, która operuje na snapshotach stanu, detekcja w czasie rzeczywistym opiera się na wykrywaniu anomalii bliskich skutkowi — nie na dopasowaniu do „magicznego podpisu ataku”.

Kluczowe klasy sygnałów

  • Bruteforce i nieautoryzowane próby dostępu — skoki błędów logowania, połączenia z nietypowych źródeł, nagły wzrost liczby nowych sesji. Parametry log_connections i log_disconnections w połączeniu z analizą logów pozwalają identyfikować tego typu aktywność na wczesnym etapie.
  • Eskalacja uprawnień — pojawienie się nowych ról, nieoczekiwane nadanie członkostw w grupach, uprawnienia nieproporcjonalnie szerokie względem profilu aplikacji.
  • Eksfiltracja danych — nietypowe, kosztowne odczyty na wrażliwych tabelach, masowe eksporty, nagłe zmiany profilu zapytań. Statystyki z pg_stat_statements są tu często bardziej użyteczne niż same logi. Osobnym wektorem są nieautoryzowane sloty replikacyjne — pg_replication_slots i pg_stat_replication pozwalają wykryć sloty, które mogą służyć do cichego wyprowadzania danych poza klaster.
  • DoS na warstwie bazy — lawina połączeń lub blokad; długie transakcje trzymające blokady wykrywalne przez korelację pg_stat_activity z pg_locks i przypisanie do konkretnej roli lub aplikacji.
  • „Cichy sabotaż” — wyłączenie logowania, zmiana parametrów obniżających widoczność zdarzeń (np. log_min_duration_statement, log_statement), manipulacja retencją logów. To sygnał szczególnie groźny, bo eliminuje ślad dalszej aktywności atakującego.

Widoki systemowe jako źródło sygnałów

pg_stat_activity ujawnia aktywne połączenia, ich adresy źródłowe, wykonywane zapytania i stany — nieoczekiwane połączenie superusera z nieznanego adresu IP to sygnał alarmowy najwyższego priorytetu. pg_stat_ssl wskazuje z kolei sesje bez szyfrowania TLS, co w środowisku produkcyjnym stanowi naruszenie zarówno polityki PCI-DSS, jak i rekomendacji CIS Benchmark.

Ważne zastrzeżenie metodyczne

Monitorujemy przede wszystkim metadane i wzorce dostępu — nie dane biznesowe. Tam, gdzie kontrole odnoszą się do zawartości tabel (opisane poniżej), przyjmuje się bezpieczną formę: kontrola spójności, liczności, sum kontrolnych lub wykrywanie nieautoryzowanych zmian w wybranych obiektach krytycznych — bez utrwalania wrażliwej treści w narzędziu monitorującym.

Domknięcie pętli: od sygnału do działania

Monitoring bezpieczeństwa ma sens wyłącznie wtedy, gdy kończy się konkretnym działaniem — sam sygnał bez zdefiniowanej reakcji jest tylko szumem informacyjnym. Fundamentem skutecznego procesu są jasne progi klasyfikacji: precyzyjne rozróżnienie między incydentem a ostrzeżeniem eliminuje zmęczenie alertami i pozwala zespołowi skupić uwagę tam, gdzie jest naprawdę potrzebna.

Wszędzie, gdzie pozwala na to profil ryzyka, warto wdrożyć reakcje automatyczne — blokadę źródła połączenia, tymczasowe ograniczenie uprawnień roli czy odcięcie nieautoryzowanego eksportu — skracając tym samym czas od wykrycia do neutralizacji zagrożenia. Automatyzacja nie zastąpi jednak ludzkiego osądu w przypadkach niejednoznacznych, dlatego niezbędnym uzupełnieniem są playbooki operacyjne dla zespołu: ustrukturyzowane instrukcje określające, co sprawdzić w pg_stat_activity, które logi przejrzeć, jakich różnic szukać w pg_settings i jak zinterpretować nagłe zmiany w strukturze ról.

Dobrze zaprojektowana pętla reagowania zamienia monitoring z narzędzia obserwacyjnego w realny mechanizm obronny: sygnał → klasyfikacja → automatyczna reakcja lub eskalacja do playbooku → dokumentacja → korekta baseline.

Katalogi systemowe PostgreSQL do monitorowania

Poniższy rozdział stanowi rozwiniętą listę kontrolną opartą na analizie 21 wewnętrznych katalogów i widoków systemowych PostgreSQL 16, które powinny podlegać ciągłemu monitorowaniu bezpieczeństwa. Każda pozycja jest zmapowana na konkretne standardy branżowe — CIS PostgreSQL Benchmark v1.1+, NIST 800-53, PCI-DSS 4.x, ISO 27001 oraz OWASP PostgreSQL Security Cheat Sheet — i zawiera przykładowe zapytanie SQL służące do detekcji. Tabela obejmuje zarówno katalogi uwierzytelniania i zarządzania rolami (pg_authid, pg_roles, pg_shadow), jak i widoki monitorujące szyfrowanie sesji (pg_stat_ssl), aktywne połączenia (pg_stat_activity), sloty replikacyjne (pg_replication_slots), zainstalowane rozszerzenia (pg_extension), funkcje z flagą SECURITY DEFINER (pg_proc) czy reguły uwierzytelniania klientów (pg_hba_file_rules).

Mapowanie na zagrożenia — eskalacja uprawnień, backdoor users, słabe uwierzytelnianie, wykonanie kodu, eksfiltracja danych, ruch lateralny i ruch nieszyfrowany — pozwala priorytetyzować monitoring względem realnych scenariuszy ataku. Jako absolutne minimum (non-negotiable baseline) dokument wskazuje 10 katalogów: pg_authid, pg_roles, pg_db_role_setting, pg_hba_file_rules, pg_settings, pg_proc, pg_extension, pg_user_mapping, pg_stat_activity oraz pg_stat_ssl.

Lista kontrolna 1: katalogi systemowe Postgres do monitorowania bezpieczeństwa

Poniżej znajduje się lista wewnętrznych katalogów systemowych i widoków PostgreSQL, które należy monitorować pod kątem zagrożeń bezpieczeństwa, zgodnie z:

  • ✅ PostgreSQL 16 Best Practices
  • ✅ CIS PostgreSQL Benchmark (v1.1+)
  • ✅ OWASP PostgreSQL Security Cheat Sheet
  • ✅ NIST 800-53 / PCI-DSS principles
Ustawienie Kategoria Standard Bazowy kod SQL do monitorowania
pg_catalog.pg_authid Uwierzytelnienie CIS, NIST, PCI-DSS SELECT rolname, rolsuper, rolcreaterole, rolcreatedb FROM pg_authid;
pg_catalog.pg_shadow Uwierzytelnienie (legacy) CIS SELECT usename, passwd IS NOT NULL AS has_password FROM pg_shadow;
pg_catalog.pg_user Konto użytkownika CIS SELECT * FROM pg_user;
pg_catalog.pg_roles Zarządzanie rolami CIS, ISO 27001 SELECT rolname, rolinherit, rolcanlogin FROM pg_roles;
pg_catalog.pg_db_role_setting Ustawienia ról CIS SELECT * FROM pg_db_role_setting;
pg_catalog.pg_user_mapping Poświadczenia FDW CIS, OWASP SELECT * FROM pg_user_mapping;
pg_catalog.pg_foreign_server Dostęp zewnętrzny CIS SELECT * FROM pg_foreign_server;
pg_catalog.pg_foreign_data_wrapper Dostęp zewnętrzny CIS SELECT * FROM pg_foreign_data_wrapper;
pg_catalog.pg_hba_file_rules Uwierzytelnienie klienta CIS, OWASP SELECT * FROM pg_hba_file_rules WHERE auth_method IN ('trust','password');
pg_catalog.pg_settings Konfiguracja CIS, NIST SELECT name, setting FROM pg_settings WHERE source NOT IN ('default','override');
pg_catalog.pg_proc Funkcje i procedury CIS, OWASP SELECT proname, prosecdef FROM pg_proc WHERE prosecdef;
pg_catalog.pg_language Języki procedur CIS SELECT * FROM pg_language WHERE lanpltrusted = false;
pg_catalog.pg_extension Rozszerzenia CIS, OWASP SELECT * FROM pg_extension;
pg_catalog.pg_largeobject Eksfiltracja danych OWASP SELECT COUNT(*) FROM pg_largeobject;
pg_catalog.pg_stat_activity Aktywne sesje NIST, PCI-DSS SELECT pid, usename, client_addr, state FROM pg_stat_activity;
pg_catalog.pg_stat_ssl Szyfrowanie CIS, PCI-DSS SELECT * FROM pg_stat_ssl WHERE ssl = false;
pg_catalog.pg_replication_slots Replikacja CIS SELECT * FROM pg_replication_slots;
pg_catalog.pg_stat_replication Replikacja CIS SELECT client_addr, state FROM pg_stat_replication;
pg_catalog.pg_event_trigger Monitoring DDL CIS SELECT * FROM pg_event_trigger;
pg_catalog.pg_tables Własność obiektów CIS SELECT schemaname, tablename, tableowner FROM pg_tables;
pg_catalog.pg_namespace Uprawnienia schematu CIS SELECT nspname, nspowner FROM pg_namespace;

🔍 Mapowanie zagrożeń

Zagrożenia Katalogi / Widoki
Eskalacja uprawnień pg_authid, pg_roles, pg_db_role_setting
Ukryci użytkownicy (backdoor) pg_user, pg_shadow, pg_authid
Słabe uwierzytelnienie pg_hba_file_rules, pg_settings
Wykonywanie nieautoryzowanego kodu pg_proc, pg_language, pg_extension
Eksfiltracja danych pg_foreign_*, pg_replication_*, pg_largeobject
Ruch wschód-zachód (Lateral Movement) pg_user_mapping, pg_event_trigger
Komunikacja nieszyfrowana pg_stat_ssl

Podsumowanie pokrycia standardami i zaleceniami

Standard Pokrycie
CIS PostgreSQL Benchmark ✅ Bezpośrednie mapowanie
NIST 800-53 (AU, AC, IA) ✅ Audyt + dostęp
PCI-DSS 4.x ✅ Monitoring użytkowników i dostępu
ISO 27001 A.9 / A.12 ✅ Dostęp + logowanie

Końcowe rekomendacje

Minimalny poziom bezpieczeństwa monitorowania

  1. (niepodlegający negocjacjom):
    • pg_authid
    • pg_roles
    • pg_db_role_setting
    • pg_hba_file_rules
    • pg_settings
    • pg_proc
    • pg_extension
    • pg_user_mapping
    • pg_stat_activity
    • pg_stat_ssl

Wykrywanie zaawansowanych śladów intruzji (Stealth Traces)

Ten rozdział wprowadza listę kontrolną poświęconą zaawansowanym technikom maskowania, które atakujący stosują wewnątrz PostgreSQL, wykorzystując jego w pełni legalne mechanizmy. Obejmuje monitorowanie czterech kluczowych katalogów — pg_database (ukryte bazy z wyłączoną flagą datallowconn), pg_namespace (spoofing przestrzeni nazw i manipulacja search_path), pg_class (obiekty w nietypowych schematach, nieautoryzowane tabele TOAST, podmiana właścicieli) oraz pg_attribute (kolumny-backdoory dodawane do wrażliwych tabel) — a także zaawansowane wektory związane z pg_constraint i grafem zależności (pg_depend): constrainty typu CHECK wywołujące złośliwe funkcje użytkownika, constrainty NOT VALID / NOT ENFORCED dodawane „po cichu”, oraz obiekty podpięte pod rozszerzenia w celu ukrycia proweniencji.

Każdy wektor jest opisany w formacie: zagrożenie, powiązane standardy (CIS, NIST, OWASP, PCI-DSS, ISO 27001), zapytanie detekcyjne SQL i rekomendowane środki zaradcze, tworząc gotowy do wdrożenia operacyjny runbook. Nadrzędna strategia opiera się na pięciu zasadach: Baseline Everything (codzienne snapshotowanie katalogów), Diff, Don’t Just Query (porównywanie hashy, nie tylko bieżących wartości), Enforce DDL Monitoring (event triggers na ddl_command_end), Lock Down Critical Objects (stali właściciele, REVOKE CREATE/ALTER) oraz Assume Misuse of Legitimate Features — bo wszystko, co tu opisano, to normalne zachowanie PostgreSQL, i właśnie dlatego atakujący z niego korzystają.

Lista kontrolna 2: wykrywanie zaawansowanych śladów intruzji w Postgres

1️⃣ pg_database.datallowconn

⚠️ Zagrożenie

  • Atakujący mogą zablokować połączenia z bazą danych, aby:
    • Ukrywać złośliwe obiekty
    • Opóźniać wykrywanie
    • Zakłócać audyty i kopie zapasowe
  • to nie jest normalny stan poza przerwami konserwacyjnymi.

🎯 Standardy

  • CIS: Database availability and configuration drift
  • NIST AC-4, AU-6
  • ISO 27001 A.12.1

🔍 Kod SQL do wykrywania

SELECT datname, datallowconn
FROM pg_database
WHERE datallowconn = false
  AND datname NOT IN ('template0');

🛡️ Przeciwdziałanie

  • Alarmowanie o każdej zmianie w datallowconn
  • Wymuszanie żądania zmiany lub ustawienia flagi “konserwacja w toku”
  • Logowanie każdego wywołania ALTER DATABASE

2️⃣ pg_namespace – zmiany właściciela i nadużywanie search_path

⚠️ Zagrożenie

  • Tworzenie schematów poprzedzających pg_catalog w search_path (atak typu search_path hijacking)
  • Maskowanie złośliwych obiektów poprzez nazewnictwo imitujące katalogi systemowe (np. trick public.pg_authid)
  • Nieautoryzowane zmiany właściciela schematu

🎯 Standardy

  • CIS: Secure schema management
  • OWASP: Namespace confusion attacks

🔍 Kod SQL do wykrywania

-- Niestandardowi właściciele schematów
SELECT nspname, pg_get_userbyid(nspowner) AS owner
FROM pg_namespace
WHERE nspname NOT IN ('pg_catalog','information_schema','public')
  AND nspowner NOT IN (
      SELECT oid FROM pg_roles WHERE rolname IN ('postgres')
  );
-- Niebezpieczne ustawienia ścieżki wyszukiwania
SELECT r.rolname, s.setconfig
FROM pg_db_role_setting s
JOIN pg_roles r ON r.oid = s.setrole
WHERE s.setconfig::text LIKE '%search_path%'
  AND s.setconfig::text !~ '^\\{pg_catalog';

🛡️ Przeciwdziałanie

  • Wymuszanie ustawienia search_path = pg_catalog, public
  • Zakaz umieszczania schematów kontrolowanych przez użytkownika przed pg_catalog w ścieżce wyszukiwania pg_catalog
  • Monitorowanie tworzenia schematów i zmian ich właściciela

3️⃣ pg_class: obiekty w niestandardowych schematach i typach

⚠️ Zagrożenie

  • Ukryte tabele lub indeksy w mało widocznych (obscure) schematach
  • Nieautoryzowane tabele TOAST (The Oversized-Attribute Storage Technique tj. tabele pomocnicze dla większych wartości atrybutów)
  • Podmiana właściciela obiektu maskująca faktyczną kontrolę nad nim
  • Nietypowe użycie pola relkind w pg_class (określające typ relacji: tabela, indeks, widok itd.)

🎯 Standardy

  • CIS: Object integrity
  • NIST SI-7, AU-6

🔍 Kod SQL do wykrywania

-- Obiekty w niestandardowych schematach
SELECT n.nspname, c.relname, c.relkind
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname NOT IN ('pg_catalog','information_schema','public');
-- Nieoczekiwany typ relacji
SELECT relname, relkind
FROM pg_class
WHERE relkind NOT IN ('r','i','S','v','m','t','p');
-- Tabele pomocnicze TOAST niepowiązane z tabelami głównymi
SELECT c.relname
FROM pg_class c
WHERE relname LIKE 'pg_toast_%'
AND NOT EXISTS (
  SELECT 1 FROM pg_class t WHERE t.reltoastrelid = c.oid
);

🛡️ Przeciwdziałanie

  • Objęcie wszystkich obiektów baselinem (inwentaryzacja stanu bazowego)
  • Alarmowanie o operacjach DDL wykonanych poza potokiem wdrożeniowym
  • Zablokowanie możliwości zmiany właściciela krytycznych schematów

4️⃣ pg_attribute: backdoory na poziomie kolumn

⚠️ Zagrożenie

  • Dodawanie ukrytych kolumn do wrażliwych tabel
  • Ciche wyprowadzanie (siphoning) danych za pomocą triggerów (trigger-based exfiltration)
  • Omijanie logiki aplikacji

🎯 Standardy

  • CIS: Change control
  • PCI DSS: Schema integrity
  • ISO 27001 A.12.1

🔍 Kod SQL do wykrywania

-- New columns on sensitive tables
SELECT a.attrelid::regclass AS table_name,
       a.attname AS column_name,
       a.attnum
FROM pg_attribute a
WHERE a.attnum > 0
  AND NOT a.attisdropped
  AND a.attrelid::regclass::text IN (
      'public.users',
      'public.accounts'
  );
-- Columns added recently (heuristic)
SELECT a.attrelid::regclass, a.attname
FROM pg_attribute a
JOIN pg_class c ON c.oid = a.attrelid
WHERE c.relkind = 'r'
  AND a.attnum > (
      SELECT COUNT(*) FROM information_schema.columns
      WHERE table_name = c.relname
  );

🛡️ Przeciwdziałanie

  • Wymuszenie procesu zatwierdzania operacji DDL (DDL approval workflow) tj. przejścia każdej zmiany schematu przez przegląd kodu lub potok CI/CD przed wykonaniem na instancji produkcyjnej
  • Przechwytywanie operacji ALTER TABLE za pomocą event triggerów
  • Śledzenie zmian w katalogu systemowym (catalog diffs)

🧠 Zunifikowana strategia obrony

✅ 1. Objęcie baselinem wszystkiego

Codzienne snapshotowanie kluczowych katalogów systemowych:
pg_database · pg_namespace · pg_class · pg_attribute · pg_roles · pg_proc · pg_extension

✅ 2. Porównuj stany — nie tylko odpytuj

  • Przechowywanie hashów katalogów systemowych
  • Alarmowanie o każdej nieoczekiwanej zmianie (delta)

✅ 3. Wymuszanie monitoringu DDL

CREATE EVENT TRIGGER ddl_audit
ON ddl_command_end
EXECUTE FUNCTION log_ddl_changes();

✅ 4. Chroń obiekty krytyczne przed nieuprawnionym i przypadkowym użyciem

  • Ustal stałych właścicieli obiektów krytycznych
  • Odbierz uprawnienia CREATE i ALTER wszędzie tam, gdzie to możliwe

✅ 5. Zakładaj próby nadużycia legalnych mechanizmów

Wszystkie opisane techniki to normalne zachowania PostgreSQL — i właśnie dlatego atakujący po nie sięgają.

🧾 Tabela podsumowująca

Katalog Wzorzec nadużycia Detekcja Przeciwdziałanie
pg_database Ukryte bazy danych datallowconn – nagła zmiana na false dla bazy głównej, lub true dla szablonowej Alarmowanie o ALTER DATABASE
pg_namespace Podszywanie się pod przestrzeń nazw (namespace spoofing) wykrywanie zmiany właściciela, sprawdzanie kolejności w ścieżce wyszukiwania (search_path) Wymuszenie pierwszeństwa pg_catalog
pg_class Ukryte obiekty obiekty w niestandardowych schematach nieprawidłowy typ relacji (relkind) Baseline, wymuszenie monitoringu poleceń DDL
pg_attribute Ukryte kolumny śledzenie zmian w schematach (diff) Event triggers

🔍 Dyscyplina dodatkowa: zależności w pg_constraint +

Ograniczenia (constraints) zdefiniowane w pg_catalog.pg_constraint w połączeniu z tabelą pg_depend to klasyczny wektor ukrytego ataku. Mogą bowiem:

  • wykonywać dowolne wyrażenia w klauzuli CHECK, w tym wywołania funkcji,
  • być dodawane cicho z opcją NOT VALID — bez natychmiastowego skanowania istniejących wierszy,
  • uprawdopodabniać zasadność swojego istnienia, podszywając się pod zwykłą logikę integralności danych,
  • być „podpięte” pod rozszerzenia (pg_depend, deptype = 'e’), co może maskować ścieżki własności i aktualizacji, utrudniać ustalenie proweniencji obiektu oraz chronić przed usunięciem poprzez zwykłe polecenie ALTER TABLE ... DROP CONSTRAINT

1) Wykrywanie constraintów będących składnikami< rozszerzenia („podpiętych pod rozszerzenie”)

W PostgreSQL przynależność obiektu do rozszerzenia jest reprezentowana w pg_depend przez deptype = 'e' wskazujące na pg_extension:

-- Ograniczenia będące składnikami rozszerzenia (deptype='e')
SELECT
  e.extname,
  n.nspname AS schema,
  c.relname AS table_name,
  con.conname AS constraint_name,
  con.contype,
  pg_get_constraintdef(con.oid, true) AS definition
FROM pg_constraint con
JOIN pg_class c        ON c.oid = con.conrelid
JOIN pg_namespace n    ON n.oid = c.relnamespace
JOIN pg_depend d       ON d.classid = 'pg_constraint'::regclass
                      AND d.objid   = con.oid
                      AND d.deptype = 'e'
JOIN pg_extension e    ON e.oid = d.refobjid
ORDER BY e.extname, n.nspname, c.relname, con.conname;

🛡️ Przeciwdziałanie

  • Utrzymuj białą listę dozwolonych rozszerzeń uprawnionych do posiadania obiektów (np. postgis),
  • Alarmuj, gdy jakikolwiek constraint nieoczekiwanie staje się składnikiem rozszerzenia,
  • Alarmuj o każdym nieplanowanym utworzeniu lub aktualizacji rozszerzenia (CREATE/ALTER EXTENSION)

2) Wykrywanie ograniczeń CHECK wywołujących funkcje spoza katalogu systemowego (ukryte wykonanie)

Constrainty CHECK są przechowywane w kolumnie pg_constraint.conbin i mogą być odczytane za pomocą pg_get_expr(). Wywołania funkcji wewnątrz CHECK bywają całkowicie uzasadnione, ale stanowią sygnał wysokiego priorytetu, jeśli odwołują się do funkcji spoza pg_catalog.

Poniższy wariant szuka wzorca, który wygląda jak wywołanie funkcji — czyli ciągu liter zakończonego nawiasem:

-- Ograniczenia CHECK, których wyrażenie odwołuje się do funkcji spoza pg_catalog
WITH chk AS (
  SELECT
    con.oid AS con_oid,
    n.nspname AS schema,
    c.relname AS table_name,
    con.conname,
    pg_get_expr(con.conbin, con.conrelid) AS expr
  FROM pg_constraint con
  JOIN pg_class c     ON c.oid = con.conrelid
  JOIN pg_namespace n ON n.oid = c.relnamespace
  WHERE con.contype = 'c'
)
SELECT *
FROM chk
WHERE expr ~ '\m[a-zA-Z_][a-zA-Z_0-9]*\s*\('          -- wyrażenie zawiera wywołanie funkcji
  AND expr !~ '\mpg_catalog\.'                        -- bez jawnej kwalifikacji pg_catalog
ORDER BY schema, table_name, conname;

Wariant precyzyjniejszy wykrywania (oparty na zależnościach): wyszukuje ograniczenia CHECK, które jawnie zależą od funkcji zdefiniowanej przez użytkownika. Jeśli bowiem constraint naprawdę wywołuje funkcję użytkownika, PostgreSQL tworzy wpis w pg_depend z refclassid = 'pg_proc'::regclass.w.

-- Constrainty CHECK zależne od funkcji spoza pg_catalog
SELECT
  n.nspname AS table_schema,
  c.relname AS table_name,
  con.conname AS constraint_name,
  pn.nspname AS func_schema,
  p.proname  AS func_name,
  pg_get_constraintdef(con.oid, true) AS constraint_def
FROM pg_constraint con
JOIN pg_class c        ON c.oid = con.conrelid
JOIN pg_namespace n    ON n.oid = c.relnamespace
JOIN pg_depend d       ON d.classid = 'pg_constraint'::regclass
                      AND d.objid   = con.oid
JOIN pg_proc p         ON p.oid = d.refobjid
JOIN pg_namespace pn   ON pn.oid = p.pronamespace
WHERE con.contype = 'c'
  AND d.refclassid = 'pg_proc'::regclass
  AND pn.nspname <> 'pg_catalog'
ORDER BY table_schema, table_name, constraint_name, func_schema, func_name;

Oba warianty wyszukiwania warto stosować łącznie: wariant 2 jako sygnał wysokiego priorytetu (precyzyjny, mało szumu), wariant 1 jako sieć o szerszych oczkach — do wykrycia przypadków, gdzie zależność mogła nie zostać w pełni zarejestrowana.

🛡️ Przeciwdziałanie

  • Preferuj ograniczenia oparte wyłącznie na prostych wyrażeniach z funkcjami IMMUTABLE,
  • Alarmuj o ograniczeniach zależnych od funkcji spoza pg_catalog,
  • Alarmuj o nowych i zmienionych funkcjach w pg_proc, szczególnie oznaczonych jako SECURITY DEFINER

3) Constrainty jako wektor ukrycia: NOT VALID, NOT ENFORCED i odroczenia weryfikacji

Atakujący chętnie sięgają po NOT VALID, ponieważ opcja ta pomija skanowanie istniejących wierszy — dodanie constraintu jest szybkie i generuje mniej szumu w logach:

-- Constrainty bez walidacji (typowa technika ukrywania zmian) 
SELECT
  n.nspname AS schema,
  c.relname AS table_name,
  con.conname AS constraint_name,
  con.contype,
  con.convalidated,
  con.condeferrable,
  con.condeferred,
  pg_get_constraintdef(con.oid, true) AS definition
FROM pg_constraint con
JOIN pg_class c     ON c.oid = con.conrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE con.contype IN ('c','f','u','p')
  AND con.convalidated = false
ORDER BY schema, table_name, constraint_name;

PostgreSQL 17 rozszerza obsługę NOT ENFORCED również na klucze obce — gdy constraint jest oznaczony tym atrybutem, powiązane triggery integralności są w ogóle nie tworzone (lub usuwane), a sam constraint automatycznie otrzymuje status NOT VALID. Z perspektywy wykrywania zagrożeń należy traktować NOT ENFORCED analogicznie jak NOT VALID.

Mechanizm DEFERRABLE INITIALLY DEFERRED odkłada sprawdzenie constraintu do momentu zatwierdzenia transakcji (COMMIT) — w normalnym użyciu służy to obsłudze złożonych operacji, gdzie pośredni stan danych chwilowo narusza integralność (np. przeniesienie węzła w drzewie). Atakujący może jednak wykorzystać to okno czasowe, w którym baza danych akceptuje stan teoretycznie niemożliwy, co otwiera pole do ataków typu side-channel lub manipulacji logiką biznesową, by w trakcie transakcji wstawić dane naruszające regułę CHECK (np. wywołując złośliwą funkcję), a następnie je wycofać (ROLLBACK) — efekt uboczny funkcji (zapis do tabeli audytowej, wywołanie pg_notify, dblink) zostaje jednak wykonany, mimo że transakcja nie pozostawia śladu w głównej tabeli.

🛡️ Przeciwdziałanie

  • Alarmuj o każdym ograniczeniu z convalidated=false.
  • Wymagaj formalnego zgłoszenia (ticket) i następczej walidacji: VALIDATE CONSTRAINT.
  • Inwentaryzuj wszystkie constrainty z condeferrable = true i condeferred = true w pg_constraint — każdy taki obiekt powinien mieć udokumentowane uzasadnienie biznesowe.
  • Traktuj DEFERRABLE na constraintach CHECK wywołujących funkcje jako sygnał wysokiego priorytetu, szczególnie w połączeniu z SECURITY DEFINER

4) „Nieoczekiwane zależności” w szerszym ujęciu: constrainty powiązane z nietypowymi obiektami

Mechanizm „podpięcia” nie ogranicza się do funkcji — constraint może tworzyć zależności również od typów danych, operatorów, sortowań (collations) i innych obiektów katalogowych.

Poniższe zapytanie pokazuje, od czego zależy każdy constraint, w czytelnej dla człowieka formie:

-- Obiekty, od których zależą poszczególne constrainty (przegląd pod kątem nieoczekiwanych odwołań) 
SELECT
  n.nspname AS schema,
  c.relname AS table_name,
  con.conname AS constraint_name,
  d.deptype,
  pg_describe_object(d.refclassid, d.refobjid, d.refobjsubid) AS depends_on
FROM pg_constraint con
JOIN pg_class c     ON c.oid = con.conrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_depend d    ON d.classid = 'pg_constraint'::regclass
                   AND d.objid   = con.oid
WHERE con.contype IN ('c','f','u','p')
ORDER BY schema, table_name, constraint_name, d.deptype, depends_on;

🛡️ Przeciwdziałanie

  • Zbuduj baseline „normalnego grafu zależności” dla każdej bazy danych.
  • Alarmuj o każdej nowej krawędzi grafu zależności, szczególnie wskazującej na:
    • funkcje lub operatory spoza pg_catalog,
    • obiekty będące własnością rozszerzenia,
    • obiekty w niestandardowych schematach.
  • Alarmuj o każdym nieplanowanym utworzeniu lub aktualizacji rozszerzenia (CREATE/ALTER EXTENSION)

Kontrole operacyjne — realne środki zaradcze

✅ Zapobieganie

  • Ogranicz uprawnienia do wykonywania DDL: minimalizuj CREATE na schematach, unikaj szerokiego nadawania ALTER,
  • Rozdziel role wdrożeniowe od ról runtime (reguła separacji obowiązków),
  • Restrykcyjna polityka rozszerzeń: zezwalaj wyłącznie na zatwierdzone rozszerzenia, ogranicz CREATE EXTENSION.

✅ Wykrywanie

  • Przechwytuj operacje DDL za pomocą event triggerów, w szczególności:
    • ALTER TABLE ... ADD CONSTRAINT
    • CREATE/ALTER EXTENSION
  • Włącz audyt DDL za pomocą pgaudit tam, gdzie jest to możliwe.

✅ Reagowanie

  • Po wykryciu podejrzanego constraintu:
    1. Zidentyfikuj jego zależności (pg_depend – patrz zapytanie z poprzedniej sekcji)
    2. Przejrzyj definicję constraintu (pg_get_constraintdef)
    3. Przeanalizuj zależne funkcje: schemat, właściciel, SECURITY DEFINER, zmienność (volatility)
    4. Usuń constraint: ALTER TABLE ... DROP CONSTRAINT ...
    5. Zbadaj role i logi w oknie czasowym odpowiadającym momentowi jego utworzenia (audyt DDL)

Pakiet zapytań do uruchamiania cyklicznego (np. raz dziennie)

Pojedynczy zestaw zapytań SQL może dostarczyć kompletnego obrazu bezpieczeństwa constraintów:

  • nowe i zmienione constrainty (w stylu snapshot diff),
  • obiekty podpięte pod rozszerzenia,
  • constrainty wywołujące funkcje spoza pg_catalog,
  • constrainty z NOT VALID,
  • skrócony „wskaźnik ryzyka” dla każdej anomalii.

Podsumowanie: Bezpieczeństwo PostgreSQL to nie jednorazowy audyt — to ciągły proces

Monitoring bezpieczeństwa PostgreSQL wykracza daleko poza standardowe sprawdzenie konfiguracji czy przegląd logów. Jak pokazują omówione w tym artykule katalogi systemowe Postgresa — od pg_authid i pg_hba_file_rules, przez pg_proc i pg_extension, aż po zaawansowane wektory stealthy takie jak pg_constraint z klauzulą NOT VALID czy nadużycia search_path w pg_namespace — atakujący nie potrzebują luk w samym silniku bazy danych. Wystarczy, że po cichu zmodyfikują to, co Postgres uznaje za normalną, legalną funkcję.

Skuteczny model ochrony łączy trzy warstwy: prewencję (minimalne uprawnienia, DDL approval workflow, polityka rozszerzeń), detekcję (snapshotting katalogów, event triggers, alarmy o dryfie konfiguracji) oraz reakcję — czyli zdefiniowane procedury na wypadek znalezienia anomalii w grafie zależności obiektów. Standardy CIS PostgreSQL Benchmark, NIST 800-53, PCI-DSS 4.x i ISO 27001 dostarczają ram formalnych, ale to operacyjna dyscyplina codziennych diffów i testów katalogowych decyduje o realnym poziomie bezpieczeństwa.

Wdrożenie tego modelu wymaga jednocześnie głębokiej znajomości mechanizmów wewnętrznych PostgreSQL, doświadczenia w projektowaniu złożonych architektur produkcyjnych oraz umiejętności integracji monitoringu z całym ekosystemem IT organizacji. Dokładnie tym zajmuje się Linux Polska.

Co konkretnie robimy dla Twoich zespołów?

Linux Polska oferuje kompleksowe usługi inżynieryjne skoncentrowane na środowiskach PostgreSQL — od projektowania architektur po hardening i ciągły monitoring bezpieczeństwa. W praktyce oznacza to:

Audyt i baseline bezpieczeństwa

  • Inwentaryzacja i ocena kluczowych katalogów systemowych pod kątem niezgodności z CIS Benchmark, NIST 800-53 i PCI-DSS
  • Identyfikacja kont z nadmiernymi uprawnieniami (rolsuper, SECURITY DEFINER), niebezpiecznych metod uwierzytelnienia (trust, password w pg_hba_file_rules) oraz niekontrolowanych rozszerzeń
  • Raport powdrożeniowy z klasyfikacją ryzyka i ścieżką remediacji

Wdrożenie monitoringu katalogów i detekcji dryfu

  • Zaprojektowanie i uruchomienie systemu snapshotów katalogowych z automatycznym diffowaniem (pg_namespace, pg_class, pg_attribute, pg_proc, pg_extension)
  • Implementacja event triggerów rejestrujących operacje DDL w środowisku produkcyjnym
  • Integracja z systemami SIEM (Elastic, Splunk, Grafana/Loki) lub istniejącym stosem monitoringu organizacji

Ochrona przed zaawansowanymi technikami stealth

  • Detekcja i eliminacja nadużyć pg_constraint — w tym klauzul NOT VALID, hooków do rozszerzeń oraz wywołań funkcji spoza pg_catalog w wyrażeniach CHECK
  • Analiza grafów zależności obiektów (pg_depend) pod kątem nieoczekiwanych powiązań z funkcjami, typami i rozszerzeniami w niestandardowych schematach
  • Utwardzanie konfiguracji search_path na poziomie ról i baz danych

Projektowanie złożonych architektur HA i zgodnych z regulacjami

  • Architektura replikacji logicznej i fizycznej z uwzględnieniem kontroli pg_replication_slots i pg_stat_replication jako wektorów eksfiltracji
  • Projektowanie środowisk PostgreSQL spełniających wymogi PCI-DSS, ISO 27001 oraz DORA dla sektora finansowego
  • Wsparcie przy migration path na PostgreSQL 16, 17 i 18 z zachowaniem pełnego łańcucha kontroli bezpieczeństwa.

Szkolenia i transfer wiedzy dla zespołów DBA i DevSecOps

  • Warsztat praktyczny: „PostgreSQL Internals dla Security Engineer” — praca bezpośrednio na katalogach systemowych
  • Opracowanie wewnętrznych playbooków reagowania na incydenty bazodanowe
  • Mentoring dla zespołów wdrażających polityki hardeningu
Zobacz również