FlawOpen

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

● CVE-2014-0160 · CVSS 7.5 · 높음
보안 연구 · FlawOpen

CVE-2014-0160: OpenSSL Heartbleed 메모리 유출

CVE-2014-0160은 OpenSSL이 레코드에 없는 페이로드 길이를 신뢰할 때 TLS 또는 DTLS 피어가 Heartbeat 레코드 뒤에서 최대 64KiB를 읽게 했습니다. 업스트림 수정은 페이로드를 파싱하고 되돌려 보내기 전에 레코드를 검증합니다.[1], [2]

💡 알기 쉬운 설명 (ELI5)

몇 단어만 적힌 카드를 받은 직원이 훨씬 긴 복사본을 만들라는 라벨을 믿는 상황을 떠올려 보십시오. 카드 끝에서 멈추지 않고 공동 트레이에 이어지는 글까지 복사합니다. Heartbleed는 OpenSSL이 수신한 레코드 경계를 넘어 읽게 했으며, 노출되는 바이트는 프로세스 메모리 내용에 따라 달랐습니다.[1], [3]

핵심 개념 및 용어

TLS/DTLS Heartbeat (RFC 6520)
TLS 또는 DTLS의 연결 유지 메시지입니다. 요청에는 피어가 그대로 돌려보내는 페이로드와 프로토콜에서 요구하는 패딩이 포함됩니다.[3]
payload_length
Heartbeat 페이로드 크기를 나타내는 16비트 필드입니다. 메시지 유형, 길이 필드, 최소 패딩 뒤에 수신 레코드 안에 들어가야 합니다.[1], [3]
memcpy()
소스 포인터에서 지정한 바이트 수를 복사합니다. 소스 버퍼의 유효 길이를 알지 못하므로 호출자가 먼저 검증해야 합니다.[1], [2]
버퍼 오버리드 (CWE-126)
의도한 소스 버퍼의 끝을 넘어 읽는 것입니다. Heartbleed 응답은 메모리 내용에 따라 프로세스 메모리를 최대 64KiB까지 노출할 수 있었습니다.[5]

프레임워크 매핑

근본 원인 분석 (Root Cause)

tls1_process_heartbeat()와 dtls1_process_heartbeat()는 Heartbeat 메시지의 16비트 페이로드 길이를 읽고 응답 경로에서 이를 memcpy()에 전달했지만, s->s3->rrec.length 안에 해당 페이로드 바이트가 실제로 있는지 확인하지 않았습니다. 피어는 TLS 또는 DTLS 레코드가 담은 것보다 더 긴 길이를 선언할 수 있었습니다. 그러면 OpenSSL이 수신 레코드 뒤의 바이트까지 되돌려 보내 최대 64KiB의 프로세스 메모리를 노출할 수 있었습니다. 내용은 메모리 상태에 따라 달라 특정 키, 비밀번호 또는 쿠키가 반드시 포함되지는 않습니다. 업스트림 커밋 96db902는 먼저 유형, 길이 필드, 최소 패딩을 담기에 너무 짧은 레코드를 거부한 뒤 1 + 2 + payload + 16을 수신 레코드 길이와 비교합니다. 또한 SSL3_RT_MAX_PLAIN_LENGTH로 응답을 제한하고 계산한 write_length를 사용합니다. TLS와 DTLS 핸들러 모두에 같은 검사가 적용되었습니다.[1], [2], [3], [4]

단계별 공격 실행 흐름

단계 1

피어가 짧은 Heartbeat 레코드를 보냄

TLS 또는 DTLS 피어가 레코드에 실제로 있는 페이로드 바이트보다 큰 길이를 선언한 Heartbeat 메시지를 보냅니다.[1], [3]

단계 2

OpenSSL이 선언된 길이를 신뢰함

수정 전 핸들러는 레코드의 최소 크기를 확인하거나 페이로드를 수신 레코드와 대조하지 않은 채 유형과 길이를 읽습니다.[1], [2]

단계 3

응답이 레코드 경계를 넘어 복사함

memcpy()는 레코드 버퍼에서 선언된 바이트 수를 복사합니다. 응답에는 수신한 Heartbeat 메시지 뒤의 바이트가 포함될 수 있으며, 프로토콜 한도는 64KiB입니다.[1], [2]

단계 4

노출 내용은 프로세스 메모리에 따라 달라짐

피어가 프로세스 메모리의 바이트를 받을 수 있습니다. 특정 비밀이 포함된다고 보장할 수 없으며 결과는 메모리 내용과 엔드포인트 상태에 달려 있습니다.[1], [4]

단계 5

패치된 핸들러가 불일치를 거부함

수정은 파싱 전에 헤더와 패딩을 검사하고, 페이로드가 수신 레코드에 들어가는지 확인한 뒤 복사 전에 응답 길이를 제한합니다.[2], [1]

소스 코드 비교: 취약한 구현 vs 보안 패치

이 코드는 OpenSSL 업스트림 변경에서 발췌한 것으로 단독 실행 프로그램이 아닙니다. 취약 코드는 고정 헤더와 최소 패딩이 있는지 확인하기 전에 필드를 파싱한 다음 피어가 선언한 페이로드 길이를 복사합니다. 수정은 파싱 전에 레코드 최소 길이를 검사하고, 선언된 페이로드를 수신 레코드와 대조하며, 응답 길이를 제한합니다. OpenSSL은 tls1_process_heartbeat()와 dtls1_process_heartbeat()에 대응하는 변경을 적용했습니다.[2], [1]

소스 코드 발췌: ssl/t1_lib.c

취약한 구현
/* 수정 전 tls1_process_heartbeat()의 일부입니다. */
hbtype = *p++;
n2s(p, payload);
pl = p;

/* TLS1_HB_REQUEST 응답 경로에서: */
memcpy(bp, pl, payload);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT,
                     buffer, 3 + payload + padding);
보안 강화 패치
/* 타입, 길이, 최소 패딩을 담기에 너무 짧은 레코드는 거부합니다. */
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;

/* 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);

사고 타임라인

2012-03-14

OpenSSL 1.0.1이 배포되었으며 TLS/DTLS Heartbeat 처리에는 레코드 길이 검사가 없었습니다.[3], [1]

2014-04-07

OpenSSL은 CVE-2014-0160 권고를 발표하고 1.0.1g를 출시했으며, 권고에서 Google Security의 Neel Mehta에게 감사를 표했습니다. 1.0.2 베타 수정은 beta2에 포함되었습니다.[1], [2]

2014-04

운영자는 영향을 받은 엔드포인트에 패치를 적용하고 노출 가능성을 평가했습니다. Heartbleed.com에 따르면 일반 로그로는 응답이 어떤 메모리 바이트를 반환했는지 알 수 없습니다. 패치 후 키, 인증서, 세션 및 자격 증명 교체를 검토합니다.[6], [1]

엔지니어링 및 시스템 보안 강화 체크리스트

출처