domingo, 18 de abril de 2010

SQL injection Parte I


Sin lugar a dudas esta tecnica es una de las mas peligrosas que hay en una aplicacion, ya sea WEB o de Escritorio, por la potencia que tiene y la capacidad de poder ingresar a un sistema de una forma eficaz. Por practica puedo decir que es vulnerabilidad que mas me gusta testear, ya que tiene muchas posibilidades de ataque. Decidi crear post sobre este tema dividas en partes, esta sera la primer entrega :).
Vamos a ver desde el pricipio y para que lo practiquen en forma local, asi que nesesitaremos un paquete llamado LAMPP para trabajar en entornos Linux ( tambien lo pueden hacer desde W$ ), que nos trae empaquetado APACHE, PHP, MYSQL y FTP.


¿Que es SQL Injection?
Es la Tecnica que permite introducir comandos SQL arbitrarios en una consulta que esta establecida en la aplicacion, la cual las variables que piden al usuario no pasan por algun filtro, pudiendo este introducir comandos SQL que luego seran interpretados por el DBMS.
Dependiendo el motor se inyecta de una forma u otra, este tutorial va a estar basado en MySQL con PHP.


Requisitos:
  1. Descargar Lammpp Ultima Version
  2. Conocimientos en PHP y MySQL
  3. Conocimientos de phpMyAdmin

Comenzando

1)Instalando lampp

Como dije anteriormente,
el tutorial enseñara hasta como instalar el lampp, de forma básica podría encontrar mas datos sobre el, en su pagina principal.
Cuando tengamos el archivo de lampp que en este caso seria este xampp-linux-1.7.3a.tar.gz, para instalarlo tiene una forma super fácil, nos logueamos como r00t y colocamos en la consola

#tar xvfz xampp-linux-1.7.3a.tar.gz -C /opt

Esto creara una carpeta llamada lampp, en el directorio /opt. Por lo tanto todos los archivos, binarios, conflagraciones, bases de datos, etc estarán en el /opt/lampp.
Podemos ir posicionandonos en ella e ir familiarizandonos,

#cd /opt/lampp; ls -la

La conflagración del Lampp por ahora la vamos a dejar como default, para poder hacer inyecciones copadas sobre MySQL. Lo que si vamos a cambiar es el DocumentRoot
de apache, que es la raíz lógica de donde apache empieza a leer los documentos, para ellos tenemos que editar el archivo httpd.conf de Apache


#cd /opt/lampp/etc
Realizamos un backups antes de realizar cambios en archivos de configuraciones de cualquier tipo.
#cp httpd.conf httpd.conf.backup
#nano httpd.conf

Yo uso nano uds podrian usar VI o el que quisieran, buscamos la linea donde dice

DocumentRoot "/opt/lampp/htdocs"

Y cambiamos a una carpeta en nuestro home, osea en mi caso

DocumentRoot "/home/magnobalt/www"

Una vez hecho esto guardamos, abrimos otra shell, como usuario comun y creamos la carpeta www en nuestro home

$mkdir /home/magnobalt/www

Listo con esto tenemos configurado para que apache venga a leer a esta carpeta y busque los archivos para ejecutar en el servidor. Aquí es donde vamos a colocar nuestro archivos html, js, php, css, etc.
Para alzar los servicios de lampp hacemos lo siguiente como r00t.

#/opt/lampp/lampp start
Starting XAMPP for Linux 1.7.2...
XAMPP: Starting Apache with SSL (and PHP5)...
XAMPP: Starting MySQL...
XAMPP: Starting FTP ..
XAMPP for Linux started.

Con esa informacion significa que todos los servicios estan corriendo sin problemas, en caso de que haya problema les avisara con una leyenda, pero si no tocaron nada todo correra perfectamente.
Para poder empezar podemos ir a nuestro navegador y escribir http://localhost o http://127.0.0.1, lo cual es la ip de loopback, osea que ingresamos a nuestra propia maquina,
como nos tenemos ningun archivo index,html, index.php etc, todavia en nuestro DocumentRoot en mi caso /home/magnobalt/www, seguramente mostrara seguramente un error 403 Fordiben.
Para poder ingresar al PhpMyAdmin colocamos en el browser http://localhost/phpmyadmin, se daran cuenta que entra directamente, eso es por que al instalar lampp, la contraseña del usuario root en mysql esta en blanco, Obivamente esto no deberia quedar asi, se tendria que colocar una, pero aqui obviaremos.



Primer SQL Injection:

Lo que primero vamos a realizar es un bypass a un login, esta inyeccion es la mas facil de entender, ¿Bypass WTF!!?. El termino Bypass significa saltarse algun tipo
de seguridad, por ejemplo, si un programador me coloca en una funcion que una variable no acepte numeros enteros, y yo de alguna forma logro que la aplicacion
tome numeros enteros, estoy bypaseando (saltando) la seguridad del programador. :)

Lo que haremos es crear un Panel de Administrador, donde pedira usuario y contraseña, pero antes que nada para esto nesesitamos crear una Base de Datos, y una tabla
llamada Usuarios. Para ellos usaremos el PhpMyadmin.




En la imagen se puede observar como estamos creando una tabla llamada usuarios con los compos, id, usuario, password, nombre y correo. EL campo id es nuestra clave y es incremental.


En SQL seria de esta forma:

    CREATE TABLE `hacking`.`usuarios` (
`id` INT( 2 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`usuario` VARCHAR( 20 ) NOT NULL ,
`password` VARCHAR( 20 ) NOT NULL ,
`nombre` VARCHAR( 35 ) NOT NULL ,
`correo` VARCHAR( 50 ) NOT NULL
) ENGINE = MYISAM ;


Ahora que tenemos la tabla, vamos a crear los archivos php, como el panel siempre esta en una carpeta
llamada, admin, administrador, administrator etc. Vamos a crear una carpeta llamada admin en nuestro DocumentRoot que va hacer donde vamos a guardar los archivos del Login, lo cual quedaria /home/magnobalt/www/admin

Los archivos que vamos a nesesitar van hacer index.php, login.php y estilo.css


index.php




<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />

<title>Hacking Nea</title>

<link rel="STYLESHEET" type="text/css" href="../estilo.css">



<body>

<div id="contenedor">

<div id="encabezado">





</div>

<!--fin de encabezado-->



<div id="menu">





</div>



<div id="areatexto">



<div id="cuadrodialogo">

<h1><b>Sistema de Logueo ZamBonet</b></h1>

<form action="login.php" method="post">



<br>Nombre:

<br>

<input type="text" name="nombre" maxlength="8">

<br>

<br>

Contrasena:

<br>

<input type="password" name="pass" maxlength="80">

<br>

<input type="submit" value="Enviar">



</form>



</div>





</div>

<!--fin areatexto-->











<div id="pie">

<!--fin pie-->

</div>

<!--fin contenedor-->





</body>

</html>


Este archivo es el formulario que pedira al usuario que ingrese el usuario y el password, el cual los datos ingresados son procesados por un archivo llamado login.php.

login.php

<?php

$hostname = "localhost";

$database = "hacking";

$username = "root";

$password = "";

$conexion = mysql_connect($hostname, $username, $password) or die ('Error conexion'.mysql_error());



$usuario=$_POST['nombre'];

$password=$_POST['pass'];



mysql_select_db($database,$conexion);

$sql="SELECT * FROM usuarios WHERE usuario='$usuario' AND password='$password'";

echo "Esta es la Consulta: ".$sql;

echo "<br>";echo "<br>";



$login=mysql_query($sql,$conexion) or die ('Error en la consulta'. mysql_error());



$row=mysql_fetch_array($login);

if(isset($row) && !empty($row)){ //verifico que la variable $row tenga informacion

echo "<br>";

echo "<h1><b>ACCESO PERMITIDO<b></h1>";

echo "<br>";

echo "Estos son tus datos";

echo "<br>";echo "<br>";

echo "Usuario: ".$row['usuario'];

echo "<br>";

echo "Password: ".$row['password'];

echo "<br>";

echo "Email: ".$row['correo'];

}

else

{

echo "<h1><b>ERRORRRRRR PAYASO <b></h1>";

}



?>




Las primeras lineas es la conexion de la base de datos, la base de datos que contiene la tabla Usuarios, que creamos anteriormente esta en una llamada Hacking, uds quizas la llamaron de otra forma, el usuario que esta realizando las consultas es r00t (super usuario de MySQL) , lo cual es un grave error esto nunca deberia suceder se tendria que crear un usuario con privilegios que la aplicacion nesesitara, r00t corre con todos los privilegios y solo se tendria que utilizar para tareas administrativas. En la practica es raro encontrar este caso pero se encunetran, mas en los lugares donde tienen un servidor dedicado para ellos, y lo montan sin conocimientos y dejan a los servicios por default.
Nosotros vamos a correr nuestra web con r00t para que se vea lo peligroso que es y la potencia de SQL injection con estos privilegios.
Tambien se nesesita un arhicovo .css llamado estilo.css lo puden copiar desde aca, y lo guardan en la carpeta www en mi caso /home/magnobalt/www, este archivo es la maquetacion del sitio
.

Ahh me olvidaba antes de empezar con el siguiente paso que es la practica, carguen algun registro en la tabla usuario, yo coloque como usuario admin y contraseña 123456, y otro magno y contraseña qwerty carguen lo que se le ocurra.


Por fin Accion
Si todo va correctamente uds al dirigirse a http://localhost/admin, le mostrara esta pantalla.



Es el formulario donde pide el usuario y contraseña para ingresar a un area restringida, si el usuario es ingresado incorrectamente mostrara esta pantalla.




saldra la leyenda de "ERORRRRRR PAYASO" y mas arriba que esta lo interesante, es la consulta que es pasada al DBMS que en este caso es el MySQL, con esta leyenda vamos a ir observando lo que va pasando al inyectar codigo.

Si el usuario es correcto saldra esta pantalla





Como veran la sentencia SQL se sigue mostrando arriba, lo unico que hace es que si tenemos acceso sale la Leyenda "ACCESO PERMITIDO" y nos muestra los datos del Usuario.

¿Donde esta el Bug?

El error de en el codigo se encuentra en que no se filtra las variables pasadas por el formulario y se pasan automaticamente a la consulta SQL.

$usuario=$_POST['nombre'];
$password=$_POST['pass'];
$sql="SELECT * FROM usuarios WHERE usuario='$usuario' AND password='$password'";

Estas son las lineas del problema, como veran lo que nos pasa por el formulario, el usuario y password
lo llevamos a la variable $usuario y $password, luego automaticamente sin colocar alguna seguridad
lo volcamos a la consulta SQL
Si yo colocaria como nombre admin y password 123456, la consulta arrojara un TRUE y traeria datos de
la base de datos ya que para el usuario admin el password es 123456. Se veria asi


SELECT * FROM usuarios WHERE usuario='admin' AND password='123456'

Los string en MySQL son delimitados por ' (comilla simple), osea que todo lo que esta encerrados entre dos ' MySQL lo toma como string. Para poder inyectar en este caso, nosotros tendriamos que escaparnos de la comilla, simple y luego ahi colocar sentencias SQL. Para entender mejor veamos el ejemplo.
Que pasaria si en vez de colocar un nombre por ejemplo magno yo pondria una comilla simple, la consulta quedaria de este modo ( Esto se puede ir probando en su labs y cada vez que ingresen se mostrara la sentencia SQL que se esta mandando al MySQL )


SELECT * FROM usuarios WHERE usuario=''' AND password=''


Se puede ver que en el campo usuario, hay 3 comillas, las dos de afueras son las que el programador coloco para delimitar lo que ingresa el usuario como string, y la del medio es nuestra comilla simple que acabamos de enviar, lo cual arroja el siguiente error

Error en la consultaYou have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '''' AND password=''' at line 1


MySQL se esta quejando diciendo que hay un error de sintaxis mas precisamente hay una comilla simple sin cerrar. Esto significa que esa comilla simple esta siendo interpretada como codigo SQL para el DBMS, ahi esta lo interesante.

Ataque 1
Podemos lograr sin saber el password ni el usuario de alguien ingresar como el primer registro de nuestra tabla Usuarios. ¿Como? coloquemos como usuario, 'or 1=1-- , (Coloquen un espacio despues del --)
la consul
ta quedara de este modo

SELECT * FROM usuarios WHERE usuario=''or 1=1-- ' AND password=''

sin saber ni el usuario ni la contraseña entramos como admin.. ;)




Estudiemos un poco lo que hicimos, al ingresar 'or 1=1-- . La comilla ingresada por nosotros cumple la funcion de cerrar la primer comilla del programador es decir quedaria asi '', con eso ya podemos colocar sentencias SQL ya que no estamos dentro de la comillas simples, entonces viene esto or 1=1, la sentencia or significa que si algo es verdadero entonces todo es verdadero, y como 1=1 es verdadero entonces toda nuestra consulta sera verdadera por mas que no hallamos dicho el usuario es igual a '' (osea nada). Or se comporta de esta forma

1- V or V = Verdadero
2- V or F = Verdadero
3- F or V = Verdadero *
4- F or F = Falso

En nuestro caso se comportaria como la linea numero 3, osea Falso or Verdadero. Luego viene lo que es
-- ' AND password=''
, -- en sql es comentario por lo que entonces todo lo que viene despues de ahi se lo toma como comentario, esto nos permite decirle al DBMS que lo que sigue, osea
' AND password='', lo tome como un comentario. Matamos la ' simple que sobraba puesto por el programador y la parte donde pide que el password se = a lo que ingresa el usuario :)


Ataque 2

En el ataque anterior podiamos ingresar sin saber el usuario ni el password, pero caemos solamente en el primer registro de la tabla en cuestion (Usuarios).
Podemos dirigirnos a un registro conociendo el usuario y sin saber la contraeña, para ello casi siempre los administrador colocan
admin, administrador etc, como nombres de usuarios, yo en mi tabla tengo un usuario admin. Entonces inyectamos y entramos como el usuario sin saber su contraseña. En usuario colocamos admin'-- , quedando la consulta SQL

SELECT * FROM usuarios WHERE usuario='admin'-- ' AND password=''

ingresamos como admin sin saber el password :)




y como magno :)



estudiemos un poco lo que hicimos, ingresamos admin'-- la palabra admin es tomada como string ya que va a estar delimitada por la primer comilla del programador mas la comilla que ingresamos siguiendo a ella admin'. Quedando de este modo 'admin', luego el -- realiza la misma funcion que anteriormente dejando en comentario la comilla que sobra mas la sentencia donde pide el password.



La solucion

Cuando empazamos este ejemplo dijimos que la forma de poder inyectar era poder escapar de la limitaciones de la comilla simple, osea que si nosotros podemos llegar a lograr que si el usuario cuando coloque una comilla simple lo trasforme a otra cosa, el usuario ingrese lo que ingrese siempre quedara dentro de las comillas simples que el programador coloco.
Para ello tenemos formas de filtrar comillas, tenemos del lado del servidor y del lado de la aplicacion


Lado del Servidor

Una funcion muy popular es la Magic_quotes, que se encuentra en el archivo php.ini (en nuestro caso con lampp esta en /opt/lampp/etc ). Lo que realiza esta funcion es colocar automaticamente slashes, a las variables que se pasan por $_POST y $_GET. Para que sirve esto es practicamente para poder impedir las SQL injection aunque desde las versiones de PHP 5.3.0, vienen desactivadas mas info.
Para poder activar esta funcionalidad tendremos que editar el archivo php.ini, para ello nos dirigimos a la carpeta de configuracion de lampp


#cd /opt/lampp/etc
#cp php.ini php.ini.backups
#nano php.ini

y buscamos esta linea

magic_quotes_gpc = Off

Y lo ponemos en On

magic_quotes_gpc = On

Luego reiniciamos apache para que tome los cambios

#/opt/lampp/lampp restart

Ahora si quisieramos inyectar las comillas simples ' nos trasformara a esto \',
Osea que si yo ingreso nuevamente para bypasear dicho login la consulta se vera asi.

SELECT * FROM usuarios WHERE usuario='\'or 1=1-- ' AND password=''

Por lo que siempre vamos a quedarnos encerrados entres las comillas simples del programador, por lo cual ingresemos lo que ingresemos siempre nuestro datos seran tomados como un string comun :).


Lado de la Aplicación

Del lado de la aplicacion tambien existen funciones que nos permiten agregar slashes, y recomiendan usar esta funcion antes que las magic_quotes.
Addslashes, es una funcion de php que nos permite actuar de forma similar a las magic_quotes, escapando
',",\ y NULL.
Para que nuestro codigo sea capas de poder filtrar esas comillas agregaremos esta funcion a la variable que llegan desde el usuario y son pasadas para el DBMS. El codigo seguro quedaria de este modo



$usuario=$_POST['nombre'];
$password=$_POST['pass'];
$usuario= addslashes($usuario);
$password= addslashes($password);
$sql="SELECT * FROM usuarios WHERE usuario='$usuario' AND password='$password'";

Con esto logramos filtrar las comillas logrando la misma funcionalidad de magic_quotes. Recomiendo leer sobre la funcion mysql_ real_ escape_ string.
Aqui no esta pero el password se deberia guardar en Base de Datos en forma encriptada usando alguna funcion de hash como ser MD5, SHA1 etc, o algun otro algoritmo. Tambien se puede crear una funcion que al password pase 6 o N veces por la funcion HASH asi que si alguien puede verlo no pueda ser roto de forma facil con fuerza bruta o diccionario.
Por ejemplo el md5 para 123456 es e10adc3949ba59abbe56e057f20f883e, si alguien puede obetener este hash, podria pasarlo por algun diccionario que existen en la web. Una buena tools para esto es la que codeo Daniel ver aqui. Ese hash podemos ver que es facil de romper con diccionario.




El mismo password del usuario 123456, pasado 6 veces por la funcion md5 seria 74e59720dd08b1db45f7152d082c5051 , y pasada por el bruteador




Observamos dos cosas, que hay una baja taza de deteccion, y que el diccionario alimamed.pp.ru, nos mato nuestra seguridad jaja xD...







Proximas Entregas

La proxima entrega se realizara los ataques a las variables de tipo $_GET, y veran que si el dato es un entero no se nesesita la comilla simple, dependiendo de la programacion!. Se vera como detectar que la aplicacion es vulnerable y como explotar con la clausula UNION y luego como solucionar.
Y la ultima entrega se veran las SQL injection mas avanzadas que son las que permiten comprometer al HOST.
Tambien podria hacer una cuarta entrega para hablar sobre BLIND SQL, y un DOS a travez de SQL injection.

1 comentario:

  1. Interesante la perspectiva que diste para explicar SQL Injection.
    Sólo quiero agregar que hace un par de meses encontré en la página de php que la función magic_quotes estará obsoleta a partir de php 6, y enfatizan en el control en la aplicación y no confiarse en magic_quotes. Es decir, uno puede desarrollar una aplicación que se ejecute en un server con magic_quotes, pero al migrarla a un server que no lo tenga, será totalmente vulnerable. El programador no debe pensar en la solución mágica del magic_quote, debe encargarse de solventar los problemas el mismo.

    ResponderEliminar