martes, 14 de noviembre de 2017

Undo Five/Nine (Crypto 300, Lisbon CTF)

La semana pasada tuve la oportunidad de participar en la parte presencial de CTF de las Bsides de Lisboa. Hice equipo con algunos compañeros e intentamos resolver algunas de las pruebas.

Uno de los retos en los que estuve trabajando fue "Crypto 300: Undo Five/Nine". No anoté el enunciado del reto, pero basicamente te proporcionaban un trozo de código PHP "snip.php" y otros dos ficheros: "readme.txt" y "readme.txt.fsociety".

Un vistazo rápido a "snip.php" nos ayuda a entender como los otros dos ficheros son utilizados o generados:

$crypted = fopen($file . ".fsociety", "w");
$fp = fopen($file, "r+");
$clear = fread($fp, 2048);
// destroy original file
destroy_file($fp,strlen($clear));

// generate unique key
$key = gen_aes_key();
$aes = new Crypt_AES(CRYPT_AES_MODE_ECB);
$aes->setKeyLength(128);
$aes->setKey($key);

// create encrypted file
$clear = $aes->encrypt($clear);
fwrite($crypted,$clear,strlen($clear));

Como podemos ver, este código PHP lee un texto de "readme.txt" y posteriormente destruye el fichero de algún modo. Seguidamente una clave de cifrado es generada y el texto es cifrado usando AES-128 en modo ECB. El texto cifrado es guardado en el fichero "readme.txt.fsociety".

Por lo que parece, deberíamos ser capaces de recuperar el texto original de algún modo. Como la clave no es almacenada, es evidente que nos encontramos con algún tipo de debilidad en la generación de la clave. Echemos un vistazo:

function gen_aes_key() {
 $key = "";
 for ($i = 0;$i < 16;$i++)
   $key.= chr(mt_rand(0, 255));
 return $key;
}

Bueno, esto encaja con lo que pensabamos. La función "mt_rand" genera un valor aleatorio empleando el generador de números "Mersenne Twister". Esta función, tal y como su documentación avisa, no es criptograficamente segura. Después de "googlear" un poco, encontré más información sobre esta deficiencia, de donde se puede leer la siguiente información:

"Common misuses of mt_rand() include generation of anti-CSRF tokens, custom session tokens (not relying on PHP's builtin sessions support, which uses a different PRNG yet was also vulnerable until recently), password reset tokens, passwords, database backup filenames, etc. If one of these items is exposed and another is generated later without the web application or server reseeding the PRNG, then an attack is possible where the seed is cracked from the item generated earlier and is then used to infer the unknown item generated later."

Por lo que dice, según parece debería haber al menos otra llamada a "mt_rand" y deberíamos ser capaces de averiguar su resultado para ser capaces de explotar esta vulnerabilidad. Peguémosle un vistazo al código que define como se destruye el fichero original:

function destroy_file($fp,$len) {
  $random = "";
  for ($i = 0;$i < $len;$i++)
    $random.= chr(mt_rand(0, 255));
  fseek($fp, 0);
  fwrite($fp, substr($random, 0, $len));
  fclose($fp);
}

¡Bingo! El fichero es sobreescrito con valores "aleatorios" generados con la misma función, lo cual quiere decir que si somos capaces de obtener la semilla, podríamos regenerar toda la secuencia de números a partir de esta, y obtener la clave de cifrado.

¡Vamos a ello! Estuve leyendo la documentación del seed cracker, pero no resultó tan fácil como inicialmente pensé. Esta herramienta tiene varios modos de operación y no resulta obvio elegir los parámetros que necesitas. Finalmente, entendí que debía construir el comando de la siguiente forma:

./php_mt_seed [first_num] [first_num] 0 255 [second_num] [second_num] 0 255 ...

Pero como tenía unos cuantos bytes (los del fichero "readme.txt"), decidí generar los parámetros empleando algunas lineas de código PHP:

$fp = fopen("readme.txt", "r+");
$clear = str_split( fread($fp, 2048) );
foreach ($clear as $v) {
    echo ord($v) . ' ' . ord($v) . " 0 255 ";
}

Este código generó los parámetros que necesitaba, con los que lancé el seed cracker. Pasados un minuto o dos, obtuve la respuesta: "844114388". Ahora necesitamos generar toda la secuencia a partir de esta semilla. Para ello podemos usar de nuevo unas pocas lineas de código PHP:

mt_srand(844114388);
for ($i = 0;$i < 64;$i++)
    echo chr(mt_rand(0, 255));

Cuando generamos esta secuencia, podemos observar que los primeros valores coinciden con los valores contenidos en "readme.txt". Después de esos bytes podemos observar la clave de cifrado (16 bytes).

$ php gen.php  | xxd
00000000: 7b36 0ee9 f9b9 1cfe d0bb d0e6 1311 5828  {6............X(
00000010: fcfe 84a6 7453 03f6 85b6 e270 76c3 41f8  ....tS.....pv.A.
00000020: aec4 9ca5 f658 dda4 20f2 1c9f 5d14 b5b1  .....X.. ...]...
00000030: beb5 1669 3135 31f9 30bc 9438 d0ac d0d6  ...i151.0..8....

Ya solo nos queda descifrar el fichero cifrado:

$ openssl enc -aes-128-ecb -d -K "f658dda420f21c9f5d14b5b1beb51669" -in readme.txt.fsociety
flag{the_darkarmy_is_now_on_to_you}

¡Bang! ¡Conseguido! Desafortunadamente no fui capaz de puntuar con el flag ¿Por qué? Por dos motivos. El primero porque, por alguna razón, tenía en la cabeza que "snip.php" estaba generando enteros y luego truncandolos a byte, así que estuve cerca de una hora intentando entender el código y modificarlo para ajustarlo a esta circunstancia. El segundo porque el código que estaba escribiendo contra-reloj para descifrar el mensaje me dio un "syntax error" 10 segundos antes de que se acabara el tiempo del CTF, así que ya no pude arreglarlo a tiempo. En cualquier caso, me lo pasé muy bien en el CTF :)

lunes, 19 de diciembre de 2016

Ataques Evil-Maid con Hibernación


He colgado la charla que di en la última RootedCon Valencia sobre la técnica de ataque Evil-Maid explotando el fichero de hibernación de Windows.

Esta técnica no es nueva (ni la he descubierto yo), pero esta poco documentada.

También he hablado de este tipo de ataques, de forma más general en el blog de Areopago21.

En este post voy a centrarme más en la parte práctica.

Resumiendo: Si conseguimos acceso físico a un equipo y este esta encendido (pero bloqueado) o suspendido. Podemos intentar recuperar la información volátil critica (identificadores de sesión, contraseñas en claro, llaves de cifrado) del fichero de hibernación.

Para obtener el fichero de hibernación, tendremos que extraerlo del disco duro. Bien arrancando desde un dispositivo externo (Linux para forense, Hirens bootcd, etc.) o bien sacando el disco duro del equipo. Si el disco está cifrado, se complica la cosa.

Este fichero está en la raíz del disco: c:\hiberfyl.sys. Desde el propio Windows el fichero está oculto por defecto y bloqueado de forma que no puede ser leído.

El fichero de hibernación  nunca se borra, solo se modifica su cabecera cuando ha sido usado para reiniciar. De forma que si el equipo alguna vez se ha hibernado, vamos a tener este fichero ya creado. Sino, tenemos que forzar al equipo a hibernarse.

Esto es posible aunque el equipo este bloqueado, si el usuario tiene activa la opción de hibernación:


Por desgracia, a partir de Windows 7, la opción de hibernación viene deshabilitada por defecto. Aunque algunos fabricantes de portátiles la activan.

Por suerte, existe otra forma de forzar el hibernado. Si la batería llega a nivel crítico el equipo se hiberna de forma automática. Configurado por defecto en todas las versiones de Windows incluso en Windows 10.


Una vez tenemos el fichero de hibernación ya podemos trabajar sobre él.

La herramienta básica para esta tarea es Volatility, con ella podemos entre otras cosas:
• Obtener información sobre el fichero de hibernación: vol.exe hibinfo -f hiberfil.sys
• Convertirlo a formato raw: vol.exe imagecopy -f hiberfil.sys -O hiberfil.bin
• Convertirlo a formado DMP (compatible con Windbg): vol.exe raw2dmp -f hiberfil.sys -O hiberfil.dmp
• Obtener datos de navegación: vol.exe iehistory -f hiberfil.sys
• Obtener hashes de contraseñas locales: vol.exe hashdump -f hiberfil.sys
• Obtener llaves de cifrado Truecrypt: vol.exe truecryptpassphrase -f hiberfil.sys

Ejemplo de uso de Volatility:


También tenemos múltiples plugins de la comunidad para otras tareas: mimikatz, bitlocker, bitcoin, etc.

Para la conversión también podemos utilizar las herramientas de Matt Suiche (recién actualizadas), antes conocidas como MoonSols Windows Memory Toolkit. Funcionan mejor que Volatility y soportan todas las versiones de Windows hasta Windows 10.

A pesar de que tenemos un plugin de Mimikatz para Volatility es bastante limitado así que es mucho mejor trabajar directamente con Mimikatz. Para ello tenemos que:
• Convertir el fichero hiberfil.sys a un formato manejable por Windbg (DMP):
  o vol.exe raw2dmp -f hiberfil.sys -O hiberfil.dmp –profile=Win7SP0x64
• Cargar el DMP en Windbg:
  o .symfix => Configura los repositorios de símbolos de Microsoft
  o .reload => Recarga los símbolos necesarios
  o .load wow64exts => Carga el modulo para hacer debug de procesos WOW64
  o !wow64exts.sw => Activa las extensiones WOW64
• Cargar el módulo de Mimikatz en Windbg:
  o .load c:\Users\rpinuaga\Desktop\bad-hibernation\demo\mimilib64.dll => Carga el módulo de Mimikatz
  o !process 0 0 lsass.exe => Busca el proceso de lsass (Local Security Authority Subsystem Service)
  o .process /r /p fffffa800424e910 => Configura el contexto al proceso de lsass
  o !mimikatz

Y listo, aquí tenemos el resultado:


Nota: Volatility solo soporta ficheros de hibernación hasta Windows 7 (A partir de Windows 8, su formato cambia un poco). La nueva herramienta de Matt Suiche en teoría si lo permite, pero la última vez que probé el fichero resultante de la conversión tampoco era reconocido por Volatility.

miércoles, 17 de febrero de 2016

Inyección de comodines SQL en búsquedas de tipo LIKE

A continuación voy a hablar de una vulnerabilidad muy poco conocida y tradicionalmente considerada como de poco riesgo, aunque como vamos a ver en algunas situaciones puede tener un gran impacto.

Esta vulnerabilidad consiste en la posibilidad de inyectar un comodín (wildcard) en el campo de búsqueda del operador LIKE de una sentencia SQL.

OWASP menciona brevemente en sus guías este tipo de inyecciones.

En SQL tenemos 2 tipos de comodines:
  • % que equivale a cualquier cadena de cero o más caracteres.
  • _ que equivale a cualquier carácter único.

Una aplicación es vulnerable a este ataque cuando realiza una búsqueda de tipo LIKE con un parámetro recibido del usuario sin filtrar estos 2 comodines.

Por ejemplo si tenemos una aplicación con la siguiente URL:

Que muestra un texto extraído de una BBDD mediante una sentencia SQL como la siguiente:

En vez de usar la forma simple:

Aunque la variable $nombre esté saneada para evitar la inyección de SQL (por ejemplo filtrando el carácter comilla simple) sigue siendo posible la inyección de los comodines de búsqueda, de la siguiente forma:

En una aplicación de este tipo, el poder alterar la lógica de la búsqueda tal vez no sea crítico, pero ¿y si tenemos otra aplicación como la siguiente? (de la que no conocemos ningún nombre de usuario):

Podemos fácilmente hacer lo siguiente para obtener un listado de todos los usuarios disponibles:

Podemos automatizar el proceso mediante un pequeño script que nos vaya sacando los nombres de cada usuario carácter a carácter (a lo película juegos de guerra).

¿En qué situaciones puede ser peligrosa una vulnerabilidad de este tipo?:
  • En formularios de login. Me he encontrado varias veces esta vulnerabilidad en el campo “usuario” de alguno de estos formularios y menos habitualmente en el campo “contraseña”.
  • En formularios de recuperación de contraseñas. Puede permitirnos resetear la contraseña de terceros usuarios.
  • En campos que contienen identificadores de sesión o tokens. Nos podría permitir “robar” tokens o sesiones de otros usuarios.

Aunque parezca mentira esto funciona algunas veces:

La inyección del carácter % a veces puede ser problemática, porque suele estar filtrado para evitar ataques de encoding o precisamente porque es decodificado incorrectamente (en este caso podemos probar %25, %2525).

Hace tiempo me encontré una curiosa situación en una aplicación que autenticaba mediante un identificador de sesión que almacenaba en una base de datos y que extraía mediante una búsqueda vulnerable, de esta forma:


El servidor filtraba el carácter %, pero se permitía el carácter _ de forma que podíamos explotar la vulnerabilidad de la siguiente forma:


Si queríamos acceder a alguna sesión en concreto solo teníamos que hacer un barrido:


¿Porque algunos programadores son víctimas de un bug tan evidente?

Algunas veces supongo que será por despiste, pero también he detectado que algunos frameworks encapsulan las llamadas SQL de forma transparente para el programador y si internamente usan el operador LIKE, es posible que este ni siquiera lo sepa. 

Por ejemplo en Django la siguiente sentencia:

Equivale a:

Lo que puede derivar fácilmente en múltiples vulnerabilidades si el desarrollador no es cuidadoso.

O también este otro bug del mismo estilo en el módulo Propel de Symfony.