FlawOpen

flawopen.com/Teardowns/cve-2014-0160-heartbleed

● CVE-2014-0160 · CVSS 7.5 · Alta
Investigación · FlawOpen

CVE-2014-0160: fuga de memoria Heartbleed

CVE-2014-0160 permitía que un par TLS o DTLS leyera hasta 64 KiB más allá de un registro Heartbeat cuando OpenSSL confiaba en una longitud de payload que el registro no contenía. La corrección upstream valida el registro antes de analizar y devolver el payload.[1], [2]

💡 Explicación en Lenguaje Sencillo (ELI5)

Imagina que un empleado recibe una tarjeta con pocas palabras, pero confía en una etiqueta que pide una copia mucho más larga. En vez de detenerse al final de la tarjeta, sigue copiando el texto que hay después en la bandeja compartida. Heartbleed hacía que OpenSSL copiara más allá del registro recibido; los bytes expuestos dependían del contenido de la memoria del proceso.[1], [3]

Conceptos Clave y Términos

Heartbeat TLS/DTLS (RFC 6520)
Mensaje de mantenimiento de conexión en TLS o DTLS. La solicitud incluye un payload que el par devuelve, además del padding exigido por el protocolo.[3]
payload_length
Campo de 16 bits que indica el tamaño del payload Heartbeat. Debe caber en el registro recibido después del tipo de mensaje, el campo de longitud y el padding mínimo.[1], [3]
memcpy()
Copia una cantidad indicada de bytes desde un puntero de origen. No conoce la longitud válida del búfer de origen; el llamador debe validarla.[1], [2]
Lectura fuera del búfer (CWE-126)
Lectura más allá del final del búfer de origen previsto. En Heartbleed, la respuesta podía revelar hasta 64 KiB de memoria del proceso, según su contenido.[5]

Mapeos de marcos

Análisis de Causa Raíz

tls1_process_heartbeat() y dtls1_process_heartbeat() leían la longitud de payload de 16 bits de un mensaje Heartbeat y, en la ruta de respuesta, la pasaban a memcpy() sin demostrar que esos bytes existieran en s->s3->rrec.length. Un par podía declarar más datos de los que contenía el registro TLS o DTLS. OpenSSL entonces devolvía bytes posteriores al registro recibido y podía revelar hasta 64 KiB de memoria del proceso; el contenido dependía de esa memoria, por lo que no se garantizaba que apareciera una clave, contraseña o cookie concreta. El commit upstream 96db902 primero rechaza registros demasiado cortos para el tipo, el campo de longitud y el padding mínimo; después compara 1 + 2 + payload + 16 con la longitud del registro recibido. También limita la respuesta mediante SSL3_RT_MAX_PLAIN_LENGTH y usa el write_length calculado. Se aplicó el mismo patrón a los handlers TLS y DTLS.[1], [2], [3], [4]

Flujo de Ataque Paso a Paso

Paso 1

Un par envía un registro Heartbeat corto

Un par TLS o DTLS envía un mensaje Heartbeat cuya longitud de payload declarada supera los bytes de payload presentes en el registro.[1], [3]

Paso 2

OpenSSL confía en la longitud declarada

Antes de la corrección, el handler lee el tipo y la longitud sin validar primero el tamaño mínimo del registro ni comparar el payload con el registro recibido.[1], [2]

Paso 3

La respuesta copia más allá del registro

memcpy() copia la cantidad de bytes declarada desde el búfer del registro. La respuesta puede incluir bytes posteriores al mensaje Heartbeat recibido, hasta el límite de 64 KiB del protocolo.[1], [2]

Paso 4

La exposición depende del contenido del proceso

El par puede recibir bytes de la memoria del proceso. La respuesta no garantiza ningún secreto concreto; el resultado depende de la memoria presente y del estado del endpoint.[1], [4]

Paso 5

El handler corregido rechaza la incoherencia

La corrección comprueba la cabecera y el padding antes de analizar, verifica que el payload cabe en el registro recibido y limita la respuesta antes de copiar.[2], [1]

Código Fuente: Vulnerable vs. Seguro

Estos fragmentos proceden del cambio upstream de OpenSSL y no son un programa independiente. El código vulnerable analiza los campos antes de comprobar que estén presentes la cabecera fija y el padding mínimo, y después copia la longitud de payload declarada por el par. La corrección comprueba el tamaño mínimo del registro antes de analizarlo, valida el payload declarado frente al registro recibido y limita la respuesta. OpenSSL aplicó cambios equivalentes a tls1_process_heartbeat() y dtls1_process_heartbeat().[2], [1]

Fragmento de origen: ssl/t1_lib.c

IMPLEMENTACIÓN VULNERABLE
/* Fragmento de tls1_process_heartbeat() antes de la corrección. */
hbtype = *p++;
n2s(p, payload);
pl = p;

/* En la ruta de respuesta TLS1_HB_REQUEST: */
memcpy(bp, pl, payload);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT,
                     buffer, 3 + payload + padding);
PARCHE SEGURO Y ROBUSTO
/* Rechaza un registro demasiado corto para el tipo, la longitud y el relleno mínimo. */
if (1 + 2 + 16 > s->s3->rrec.length)
    return 0;

hbtype = *p++;
n2s(p, payload);
if (1 + 2 + payload + 16 > s->s3->rrec.length)
    return 0;
pl = p;

/* En la ruta de respuesta TLS1_HB_REQUEST: */
unsigned int write_length = 1 + 2 + payload + padding;
if (write_length > SSL3_RT_MAX_PLAIN_LENGTH)
    return 0;
buffer = OPENSSL_malloc(write_length);
memcpy(bp, pl, payload);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, write_length);

Cronología del Incidente

2012-03-14

OpenSSL 1.0.1 se publica con procesamiento de Heartbeat TLS/DTLS sin una comprobación de la longitud del registro.[3], [1]

2014-04-07

OpenSSL publica el aviso CVE-2014-0160 y lanza 1.0.1g; el aviso agradece el hallazgo a Neel Mehta, de Google Security. La corrección para la beta 1.0.2 llega en beta2.[1], [2]

2014-04

Los operadores parchean los endpoints afectados y evalúan la posible exposición. Heartbleed.com señala que los registros habituales no indican qué bytes de memoria devolvió una respuesta; después del parche se evalúa reemplazar claves, certificados, sesiones y credenciales.[6], [1]

Lista de Verificación de Seguridad para Ingeniería

Fuentes