FlawOpen

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

● CVE-2014-0160 · CVSS 7.5 · Alta
Pesquisa · FlawOpen

CVE-2014-0160: vazamento Heartbleed no OpenSSL

A CVE-2014-0160 permitia que um par TLS ou DTLS lesse até 64 KiB além de um registro Heartbeat quando o OpenSSL confiava em um tamanho de payload que não estava presente no registro. A correção upstream valida o registro antes de analisar e ecoar o payload.[1], [2]

💡 Explicação em Linguagem Simples (ELI5)

Imagine um funcionário recebendo um cartão com poucas palavras, mas confiando em uma etiqueta que pede uma cópia muito maior. Em vez de parar no cartão, ele continua copiando o texto que aparece depois na bandeja compartilhada. O Heartbleed fazia o OpenSSL copiar além do registro recebido; os bytes expostos dependiam do conteúdo da memória do processo.[1], [3]

Conceitos Centrais e Termos

Heartbeat TLS/DTLS (RFC 6520)
Mensagem de manutenção de conexão em TLS ou DTLS. A solicitação contém um payload que o par devolve, além do padding exigido pelo protocolo.[3]
payload_length
Campo de 16 bits que informa o tamanho do payload Heartbeat. Ele deve caber no registro recebido após o tipo da mensagem, o campo de tamanho e o padding mínimo.[1], [3]
memcpy()
Copia uma quantidade de bytes a partir de um ponteiro de origem. A função não conhece o tamanho válido do buffer; o chamador precisa validar esse limite.[1], [2]
Leitura além do buffer (CWE-126)
Leitura após o fim do buffer de origem esperado. No Heartbleed, a resposta podia expor até 64 KiB da memória do processo, dependendo do conteúdo presente.[5]

Mapeamentos de frameworks

Análise de Causa Raiz

tls1_process_heartbeat() e dtls1_process_heartbeat() liam o tamanho de payload de 16 bits de uma mensagem Heartbeat e, no caminho de resposta, o passavam a memcpy() sem comprovar que esses bytes existiam em s->s3->rrec.length. Um par podia declarar mais dados do que o registro TLS ou DTLS continha. O OpenSSL então ecoava bytes além do registro recebido, podendo expor até 64 KiB da memória do processo; o conteúdo dependia da memória, portanto não havia garantia de que uma chave, senha ou cookie específico aparecesse. O commit upstream 96db902 primeiro rejeita registros curtos demais para o tipo, o campo de tamanho e o padding mínimo; depois compara 1 + 2 + payload + 16 com o tamanho do registro recebido. Também limita a resposta com SSL3_RT_MAX_PLAIN_LENGTH e usa o write_length calculado. O mesmo padrão de validação foi aplicado aos handlers TLS e DTLS.[1], [2], [3], [4]

Fluxo de Ataque Passo a Paso

Passo 1

Um par envia um registro Heartbeat curto

Um par TLS ou DTLS envia uma mensagem Heartbeat cujo tamanho declarado de payload é maior que os bytes de payload presentes no registro.[1], [3]

Passo 2

O OpenSSL confia no tamanho declarado

Antes da correção, o handler lê o tipo e o tamanho sem validar primeiro o tamanho mínimo do registro nem comparar o payload com o registro recebido.[1], [2]

Passo 3

A resposta copia além do registro

memcpy() copia o número de bytes declarado a partir do buffer do registro. A resposta pode incluir bytes além da mensagem Heartbeat recebida, até o limite de 64 KiB do protocolo.[1], [2]

Passo 4

A exposição depende do conteúdo do processo

O par pode receber bytes da memória do processo. A resposta não garante nenhum segredo específico; o resultado depende do conteúdo da memória e do estado do endpoint.[1], [4]

Passo 5

O handler corrigido rejeita a inconsistência

A correção verifica o cabeçalho e o padding antes da análise, confirma que o payload cabe no registro recebido e limita a resposta antes da cópia.[2], [1]

Código-Fonte: Vulnerável vs. Seguro

Estes trechos vêm da alteração upstream do OpenSSL e não formam um programa independente. O código vulnerável analisa os campos antes de verificar se o cabeçalho fixo e o padding mínimo estão presentes e, depois, copia o tamanho de payload declarado pelo par. A correção verifica o tamanho mínimo do registro antes da análise, valida o payload declarado contra o registro recebido e limita o tamanho da resposta. O OpenSSL aplicou mudanças correspondentes a tls1_process_heartbeat() e dtls1_process_heartbeat().[2], [1]

Trecho de origem: ssl/t1_lib.c

IMPLEMENTAÇÃO VULNERÁVEL
/* Trecho de tls1_process_heartbeat() antes da correção. */
hbtype = *p++;
n2s(p, payload);
pl = p;

/* No caminho de resposta TLS1_HB_REQUEST: */
memcpy(bp, pl, payload);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT,
                     buffer, 3 + payload + padding);
PATCH SEGURO E ROBUSTO
/* Rejeita um registro curto demais para o tipo, o tamanho e o preenchimento 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;

/* No caminho de resposta 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);

Linha do Tempo do Incidente

2012-03-14

O OpenSSL 1.0.1 é lançado com o processamento de Heartbeat TLS/DTLS sem uma verificação do tamanho do registro.[3], [1]

2014-04-07

O OpenSSL publica o aviso CVE-2014-0160 e lança a versão 1.0.1g; o aviso agradece a Neel Mehta, da Google Security. A correção da versão beta 1.0.2 chega na beta2.[1], [2]

2014-04

Operadores corrigem endpoints afetados e avaliam a possível exposição. O Heartbleed.com observa que logs comuns não registram quais bytes de memória uma resposta devolveu; depois da correção, avalia-se a substituição de chaves, certificados, sessões e credenciais.[6], [1]

Lista de Verificação de Segurança para Engenharia

Fontes