Explicación del error Heartbleed

E

Como habrás escuchado, hay un nuevo error de OpenSSL y es malo. Este no es uno de esos errores o trucos de los que oye hablar en las noticias e ignora con seguridad como siempre lo hace. Afecta a alrededor del 66% de todos los servidores de Internet que existen, lo que probablemente incluye un sitio web que frecuenta o en el que tiene información confidencial.

Cómo funciona

Entonces, ¿cuál es el error exactamente? Para describir el error, necesitamos comprender solo algunas partes de la implementación de OpenSSL. La estructura siguiente contiene el registro SSL:

typedef struct ssl3_record_st
	{
/*r */	int type;               /* type of record */
/*rw*/	unsigned int length;    /* How many bytes available */
/*r */	unsigned int off;       /* read/write offset into 'buf' */
/*rw*/	unsigned char *data;    /* pointer to the record data */
/*rw*/	unsigned char *input;   /* where the decode bytes are */
/*r */	unsigned char *comp;    /* only used with decompression - malloc()ed */
/*r */  unsigned long epoch;    /* epoch number, needed by DTLS1 */
/*r */  PQ_64BIT seq_num;       /* sequence number, needed by DTLS1 */
/*rw*/	unsigned int orig_len;  /* How many bytes were available before padding
				   was removed? This is used to implement the
				   MAC check in constant time for CBC records.
				 */
	} SSL3_RECORD;

Esta estructura se utiliza en el dtls1_process_heartbeat(SSL *s) en el archivo d1_both.c para manejar los latidos que mantienen viva la conexión SSL. Este método accede a los datos del registro en la estructura, como se muestra a continuación:

int
dtls1_process_heartbeat(SSL *s)
	{
	unsigned char *p = &s->s3->rrec.data[0], *pl;
	unsigned short hbtype;
	unsigned int payload;
	unsigned int padding = 16; /* Use minimum padding */
    
    /* Read type and payload length first */
    hbtype = *p++;
    n2s(p, payload);
    pl = p;

El primer byte del registro SSL es el tipo de latido recibido y la macro n2s(c, l) solo toma dos bytes de p, y los pone en payload. Estos bytes son la longitud de la carga útil en el latido. Lo importante a notar aquí es que la longitud en el registro SSL no se comprueba / verifica. La variable pl ahora apunta a los datos de latidos, que fueron enviados por el cliente.

El hecho de que no se verificara la longitud del registro SSL es un gran problema, ya que más adelante en la misma función, la memoria se asigna usando la misma longitud de carga útil, que se muestra en el siguiente código:

unsigned char *buffer, *bp;
int r;

/* Allocate memory for the response, size is 1 byte
 * message type, plus 2 bytes payload length, plus
 * payload, plus padding
 */
buffer = OPENSSL_malloc(1 + 2 + payload + padding);
bp = buffer;

Ya que payload se le asignaron los primeros dos bytes de los datos de latido, que es proporcionado por el cliente, eso significa que puede tener un valor máximo de 65535 (o 64k, cuando se refiere a bytes). Entonces, esencialmente, estamos asignando la cantidad de memoria que el cliente quiere que hagamos (hasta 64k), pero no es gran cosa, ¿verdad? Asignar cantidades de datos definidas por el usuario no es muy dañino en sí mismo (aunque definitivamente no es algo bueno), pero aquí es dañino ya que más adelante en la misma función copiamos esa cantidad de memoria en nuestro búfer:

s2n(payload, bp);
memcpy(bp, pl, payload);

Aquí, s2n(c, l) mueve el tamaño de la carga útil al bp buffer (opuesto a lo que n2s hizo), y memcpy copia el número de bytes de carga útil de pl a bp. Esto tampoco parece un gran problema hasta que considere el tamaño real de pl. Y si pl es solo unos pocos bytes de tamaño? ¿O incluso solo un byte? Un atacante podría habernos dicho que la carga útil era de 64 kb, cuando solo era de 1 byte. Entonces, ¿de dónde se copiaría el resto de la memoria si pl es mucho más pequeño de lo esperado? Memoria circundante, que puede haber sido liberado recientemente del mismo proceso.

Potencialmente, hay una gran cantidad de datos confidenciales en la memoria circundante, como nombres de usuario, contraseñas e incluso claves privadas. Esto tiene a mucha gente preocupada ya que un atacante podría enviar continuamente latidos maliciosos a un servidor y seguir recuperando 64 kb de su memoria cada vez. Luego, una simple búsqueda de palabras clave como ‘contraseña’ o ‘tarjeta de crédito’ y listo, el atacante robó sus datos sin que nadie lo supiera. Mark Loman, investigador de seguridad, ya ha mostrado este ataque es posible con Yahoo Mail.

Sin embargo, por suerte para nosotros, la clave privada probablemente sea segura, como señala Neel Mehta (uno de los investigadores que descubrió el error):

Los patrones de asignación de montón hacen poco probable la exposición de claves privadas #corazón #dontpanico.

– Neel Mehta (@neelmehta) 8 de abril de 2014

Sean Cassidy proporciona información muy útil sobre estos métodos de asignación de montones:

Hay dos formas en que la memoria se asigna dinámicamente con malloc (en
menos en Linux): usando sbrk (2) y usando mmap (2). Si la memoria es
asignado con sbrk, entonces usa las viejas reglas de crecimiento del montón y
limita lo que se puede encontrar con esto, aunque múltiples solicitudes
(especialmente al mismo tiempo) todavía podría encontrar algunas cosas divertidas1.

Las asignaciones para bp no importan en absoluto, en realidad. La asignación
para pl, sin embargo, importa mucho. Es casi seguro que está asignado
con sbrk debido al umbral de mmap en malloc. Sin embargo,
cosas interesantes (como documentos o información de usuario), es muy probable que
asignado con mmap y puede ser accesible desde pl. Múltiple
las solicitudes simultáneas también pondrán a disposición algunos datos interesantes.

¿Cómo se fija?

Dado que este error es tan simple de ejecutar, eso significa que también es fácil de solucionar. OpenSSL ya ha publicado una actualización, la versión 1.0.1g. Contiene la siguiente solución:

/* Read type and payload length first */
if (1 + 2 + 16 > s->s3->rrec.length)
    return 0; /* silently discard */
hbtype = *p++;
n2s(p, payload);
if (1 + 2 + payload + 16 > s->s3->rrec.length)
    return 0; /* silently discard per RFC 6520 sec. 4 */
pl = p;

Como puede ver, las diferencias son las verificaciones de longitud, que primero aseguran que el latido no esté vacío (longitud 0) y, en segundo lugar, verifica que la carga útil del latido coincida con la longitud proporcionada por el usuario. Es así de simple. Cuatro líneas de código ahora protegen a más del 66% de los servidores del catastrófico error Heartbleed.

Cómo está parcheado

¿Ejecuta un servidor de cara al público que utiliza SSL? Entonces definitivamente deberías parchearlo. Solo puedo asumir que alguien ya ha configurado un marcador de guerra que verifica cada servidor que puede para detectar este error y luego explota los que lo tienen. Entonces, si está ejecutando Ubuntu, ejecute lo siguiente:

sudo apt-get update
sudo apt-get install -y libssl1.0.0 openssl
 
# Verify that the Build Date is April 7th 2014 or later
openssl version -a

Luego, asegúrese de reiniciar todos y cada uno de los servicios que usan OpenSSL. Si no está seguro de que el parche haya funcionado, utilice esta herramienta descubrir.

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad