Spis treści:
- Wprowadzenie
- Miejsce Monitoringu w architekturze bezpieczeństwa
- Monitoring dryfu konfiguracyjnego i ataków w czasie rzeczywistym
- Lista kontrolna 1: katalogi systemowe Postgres do monitorowania bezpieczeństwa
- 🔍 Mapowanie zagrożeń
- Podsumowanie pokrycia standardami i zaleceniami
- Końcowe rekomendacje
- Wykrywanie zaawansowanych śladów intruzji (Stealth Traces)
- Lista kontrolna 2: wykrywanie zaawansowanych śladów intruzji w Postgres
- Zunifikowana strategia obrony
- 🔍 Dyscyplina dodatkowa: zależności w pg_constraint +
- 1) Wykrywanie constraintów będących składnikami rozszerzenia („podpiętych pod rozszerzenie”)
- 2) Wykrywanie ograniczeń CHECK wywołujących funkcje spoza katalogu systemowego (ukryte wykonanie)
- 3) Constrainty jako wektor ukrycia: NOT VALID, NOT ENFORCED i odroczenia weryfikacji
- 4) „Nieoczekiwane zależności” w szerszym ujęciu: constrainty powiązane z nietypowymi obiektami
- Kontrole operacyjne — realne środki zaradcze
- Podsumowanie: Bezpieczeństwo PostgreSQL to nie jednorazowy audyt — to ciągły proces
- Co konkretnie robimy dla Twoich zespołów?
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
- (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:
- Zidentyfikuj jego zależności (pg_depend – patrz zapytanie z poprzedniej sekcji)
- Przejrzyj definicję constraintu (pg_get_constraintdef)
- Przeanalizuj zależne funkcje: schemat, właściciel, SECURITY DEFINER, zmienność (volatility)
- Usuń constraint: ALTER TABLE ... DROP CONSTRAINT ...
- 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
