본문 바로가기

1-Day 취약점 분석

OpenSSL 취약점 Heart Bleed Bug (CVE-2014-0160)

□ 취약점 요약

1. Heart Beat 기능 구현 오류를 통한 민감 정보 탈취

 

가. OpenSSL 라이브러리를 구성하는 TLS/DTLS의 확장 모듈인 Heart Beat의 기능 구현 오류로, 공격 대상 서버의 메모리에 저장된 데이터를 읽을 수 있는 취약점

나. 조작된 Heart Beat 요청을 통해 1회 최대 64KB까지 탈취 가능, 반복적인 Heart Beat 요청으로 서버에 저장된 민감한 정보를 임의적으로 탈취 가능

다. Heart Beat가 HTTP Keep-Alive 기능보다 빠르게 동작하기 때문에 암호화 세션 상태 유지를 위해 Heart Beat를 사용

2. Heart Beat 기능 구현 오류에 의한 취약성

 

가. (Heart Beat 기능) 세션 연결 유지를 위해 클라이언트는 서버에 임의의 데이터와 그 데이터의 길이를 전송, 서버는 수신한 데이터를 요청 길이에 맞게 클라이언트에 재전송
* 전달받은 데이터와 데이터 길이의 일치 여부를 확인하지 않아 Heart Beat 요청 오류시 메모리 버퍼 경계 침범 취약점 (Buffer Over Read) 발생

나. 정상적인 Heart Beat 요청

1) 클라이언트가 서버에 정상적인 Heart Beat 요청 (데이터 : Hello, 길이 정보 : 5바이트)
2) 서버는 수신한 데이터를 서버의 임의 메모리 공간에 저장
3) 서버가 해당 메모리 공간을 읽어들여(첫글자인 H부터 o까지 5바이트만) 클라이언트에 정상적인 데이터 반환

 

그림 1. 정상적인 Heart Beat 요청


다. 조작된 Heart Beat 요청을 통한 서버 데이터 탈취

1) 클라이언트가 서버에 비정상적인 Heart Beat 요청 (데이터 : Hello, 길이 정보 : 24바이트)
2) 서버가 해당 데이터를 임의 메모리 공간에 저장
3) 서버가 수신받은 데이터인 Hello의 첫 글자 H부터 24바이트 읽어들여 5 byte 데이터인 Hello 외 19byte를 클라이언트에 추가적으로 전송하여 서버의 민감한 데이터 탈취(...Root....Password)

 

그림 2. 비정상적인 Heart Beat 요청

 


3. 취약 버전

가. 취약 버전 : OpenSSL 1.0.1e ~ OpenSSL 1.0.1f, OpenSSL 1.0.2-beta, OpenSSL 1.0.2-beta1
* OpenSSL 1.0.1g, OpenSSL 1.0.0 이하 버전은 해당 취약점의 영향을 받지 않음


□ 취약점 시현


1. 시현 환경


가. 피해시스템

1) 운영체제 : Ubuntu 14.04.6 LTS
2) 웹서버 : nginx 1.4.6
3) OpenSSL : OpenSSL 1.0.1e 11 Feb 2013

나. 공격자

1) 운영체제 : Kali 2021.3
2) 공격코드 : heartbleed-poc.py
* https://github.com/sensepost/heartbleed-poc/blob/master/heartbleed-poc.py

 

2. 시현

 

가. nginx 웹서버를 통한 https 서비스 운영

 

그림 3. https 서비스

 

나. 파이썬 POC 코드를 통한 Heart Bleed 취약성 테스트 (메모리 임의 데이터 반환)

 

그림 4. POC 코드를 이용한 취약성 테스트

다. Wireshark 패킷 분석

1) Heart Beat Request

 

그림 5. Heart Beat Request 패킷


2) Heart Beat Response

 

그림 6. Heart Beat Response 패킷

 


□ 취약점 발생 부분 세부 분석

1. 소스코드 분석

 

가. 클라이언트(공격자)로부터 수신한 ssl_record에서 type,length,offset 정보를 추출하여 hbtype, payload 변수에 저장

 

그림 7. ssl3_record 정보 추출

 

나. 추출한 정보를 통해 클라이언트에 전송할 Heart Beat 응답을 구성 (피해 시스템의 메모리에서 임의 데이터를 복사하여 응답 레코드에 저장, 공격자에 해당 레코드 전송)

 

그림 8. 응답 레코드 구성

2. GDB 분석

 

가. Heart Beat 레코드 type 정보 추출 (hbtye = *p++)

1) ssl_st 구조체를 s 변수(rbp-0x38)에 저장, ssl3_record_st 구조체(p = &s -> s3 -> rrec.data[0])를 p 변수(rbp-0x20)에 저장

 

그림 9. ssl_st 구조체와 ssl3_record_st 구조체 저장


2) p변수(rbp-0x20)를 통해 ssl3_record_st의 첫 번째 변수인 type (0x1)을 hbtype변수(rbp-0x32)에 저장, p변수 값을 1증가시켜 ssl3_record_st의 두 번째 변수 length를 가리킴

 

그림 10. Heart Beat type 추출

 

나. n2s(p,payload)을 통해 payload 크기 추출

1) ssl3_record_st의 length 값 첫 번째 바이트를 가져온 후 8비트 왼쪽 시프트

 

그림 11. Heart Beat payload length 추출을 위한 시프트 연산


2) 왼쪽 시프트된 값(0x4000)과 length 값 두 번째 바이트(0x0)를 OR 연산 후 payload 변수에 저장(0x4000 값 저장, payload 크기로 16384바이트가 설정)

 

그림 12. Length 추출을 위한 OR 연산


다. HeartBeat 메세지(실질적으로는 0바이트)가 저장된 임의 메모리 공간 주소 값을 pl 변수에 저장 (p = pl)

 

그림 13. Heart Beat 메세지가 저장된 임의 메모리 공간 주소 값 저장


라. HeartBeat 응답 레코드 구성 시 임의 메모리 공간에서 0x4000 바이트 만큼 복사하여 버퍼에 저장

 

그림 14. 임의 메모리 공간에서 데이터 복사하여 Heart Beat 응답 레코드를 구성하는 버퍼에 저장

 


□ 취약함수 소스코드 전체

1. 취약점 발생 버전 소스 코드 (openssl-1.0.1e/ssl/t1_lib.c, d1_both.c)

// t1_lib.c int tls1_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; if (s->msg_callback) s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT, &s->s3->rrec.data[0], s->s3->rrec.length, s, s->msg_callback_arg); if (hbtype == TLS1_HB_REQUEST) { unsigned char *buffer, *bp; int r; /* Allocate memory for the response, size is 1 bytes * message type, plus 2 bytes payload length, plus * payload, plus padding */ buffer = OPENSSL_malloc(1 + 2 + payload + padding); bp = buffer; /* Enter response type, length and copy payload */ *bp++ = TLS1_HB_RESPONSE; s2n(payload, bp); memcpy(bp, pl, payload); bp += payload; /* Random padding */ RAND_pseudo_bytes(bp, padding); r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding); if (r >= 0 && s->msg_callback) s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding, s, s->msg_callback_arg); OPENSSL_free(buffer); if (r < 0) return r; } else if (hbtype == TLS1_HB_RESPONSE) { unsigned int seq; /* We only send sequence numbers (2 bytes unsigned int), * and 16 random bytes, so we just try to read the * sequence number */ n2s(pl, seq); if (payload == 18 && seq == s->tlsext_hb_seq) { s->tlsext_hb_seq++; s->tlsext_hb_pending = 0; } } return 0; } int tls1_heartbeat(SSL *s) { unsigned char *buf, *p; int ret; unsigned int payload = 18; /* Sequence number + random bytes */ unsigned int padding = 16; /* Use minimum padding */ /* Only send if peer supports and accepts HB requests... */ if (!(s->tlsext_heartbeat & SSL_TLSEXT_HB_ENABLED) || s->tlsext_heartbeat & SSL_TLSEXT_HB_DONT_SEND_REQUESTS) { SSLerr(SSL_F_TLS1_HEARTBEAT,SSL_R_TLS_HEARTBEAT_PEER_DOESNT_ACCEPT); return -1; } /* ...and there is none in flight yet... */ if (s->tlsext_hb_pending) { SSLerr(SSL_F_TLS1_HEARTBEAT,SSL_R_TLS_HEARTBEAT_PENDING); return -1; } /* ...and no handshake in progress. */ if (SSL_in_init(s) || s->in_handshake) { SSLerr(SSL_F_TLS1_HEARTBEAT,SSL_R_UNEXPECTED_MESSAGE); return -1; } /* Check if padding is too long, payload and padding * must not exceed 2^14 - 3 = 16381 bytes in total. */ OPENSSL_assert(payload + padding <= 16381); /* Create HeartBeat message, we just use a sequence number * as payload to distuingish different messages and add * some random stuff. * - Message Type, 1 byte * - Payload Length, 2 bytes (unsigned int) * - Payload, the sequence number (2 bytes uint) * - Payload, random bytes (16 bytes uint) * - Padding */ buf = OPENSSL_malloc(1 + 2 + payload + padding); p = buf; /* Message Type */ *p++ = TLS1_HB_REQUEST; /* Payload length (18 bytes here) */ s2n(payload, p); /* Sequence number */ s2n(s->tlsext_hb_seq, p); /* 16 random bytes */ RAND_pseudo_bytes(p, 16); p += 16; /* Random padding */ RAND_pseudo_bytes(p, padding); ret = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buf, 3 + payload + padding); if (ret >= 0) { if (s->msg_callback) s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT, buf, 3 + payload + padding, s, s->msg_callback_arg); s->tlsext_hb_pending = 1; } OPENSSL_free(buf); return ret; }

 

// d1_both.c 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; if (s->msg_callback) s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT, &s->s3->rrec.data[0], s->s3->rrec.length, s, s->msg_callback_arg); if (hbtype == TLS1_HB_REQUEST) { 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; /* Enter response type, length and copy payload */ *bp++ = TLS1_HB_RESPONSE; s2n(payload, bp); memcpy(bp, pl, payload); bp += payload; /* Random padding */ RAND_pseudo_bytes(bp, padding); r = dtls1_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding); if (r >= 0 && s->msg_callback) s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding, s, s->msg_callback_arg); OPENSSL_free(buffer); if (r < 0) return r; } else if (hbtype == TLS1_HB_RESPONSE) { unsigned int seq; /* We only send sequence numbers (2 bytes unsigned int), * and 16 random bytes, so we just try to read the * sequence number */ n2s(pl, seq); if (payload == 18 && seq == s->tlsext_hb_seq) { dtls1_stop_timer(s); s->tlsext_hb_seq++; s->tlsext_hb_pending = 0; } } return 0; } int dtls1_heartbeat(SSL *s) { unsigned char *buf, *p; int ret; unsigned int payload = 18; /* Sequence number + random bytes */ unsigned int padding = 16; /* Use minimum padding */ /* Only send if peer supports and accepts HB requests... */ if (!(s->tlsext_heartbeat & SSL_TLSEXT_HB_ENABLED) || s->tlsext_heartbeat & SSL_TLSEXT_HB_DONT_SEND_REQUESTS) { SSLerr(SSL_F_DTLS1_HEARTBEAT,SSL_R_TLS_HEARTBEAT_PEER_DOESNT_ACCEPT); return -1; } /* ...and there is none in flight yet... */ if (s->tlsext_hb_pending) { SSLerr(SSL_F_DTLS1_HEARTBEAT,SSL_R_TLS_HEARTBEAT_PENDING); return -1; } /* ...and no handshake in progress. */ if (SSL_in_init(s) || s->in_handshake) { SSLerr(SSL_F_DTLS1_HEARTBEAT,SSL_R_UNEXPECTED_MESSAGE); return -1; } /* Check if padding is too long, payload and padding * must not exceed 2^14 - 3 = 16381 bytes in total. */ OPENSSL_assert(payload + padding <= 16381); /* Create HeartBeat message, we just use a sequence number * as payload to distuingish different messages and add * some random stuff. * - Message Type, 1 byte * - Payload Length, 2 bytes (unsigned int) * - Payload, the sequence number (2 bytes uint) * - Payload, random bytes (16 bytes uint) * - Padding */ buf = OPENSSL_malloc(1 + 2 + payload + padding); p = buf; /* Message Type */ *p++ = TLS1_HB_REQUEST; /* Payload length (18 bytes here) */ s2n(payload, p); /* Sequence number */ s2n(s->tlsext_hb_seq, p); /* 16 random bytes */ RAND_pseudo_bytes(p, 16); p += 16; /* Random padding */ RAND_pseudo_bytes(p, padding); ret = dtls1_write_bytes(s, TLS1_RT_HEARTBEAT, buf, 3 + payload + padding); if (ret >= 0) { if (s->msg_callback) s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT, buf, 3 + payload + padding, s, s->msg_callback_arg); dtls1_start_timer(s); s->tlsext_hb_pending = 1; } OPENSSL_free(buf); return ret; }


2. 업데이트된 소스코드(openssl-1.0.2u/ssl/t1_lib.c, d1_both.c)

// t1_lib.c int tls1_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 */ if (s->msg_callback) s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT, &s->s3->rrec.data[0], s->s3->rrec.length, s, s->msg_callback_arg); /* 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; if (hbtype == TLS1_HB_REQUEST) { unsigned char *buffer, *bp; int r; /* * Allocate memory for the response, size is 1 bytes message type, * plus 2 bytes payload length, plus payload, plus padding */ buffer = OPENSSL_malloc(1 + 2 + payload + padding); if (buffer == NULL) return -1; bp = buffer; /* Enter response type, length and copy payload */ *bp++ = TLS1_HB_RESPONSE; s2n(payload, bp); memcpy(bp, pl, payload); bp += payload; /* Random padding */ if (RAND_bytes(bp, padding) <= 0) { OPENSSL_free(buffer); return -1; } r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding); if (r >= 0 && s->msg_callback) s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding, s, s->msg_callback_arg); OPENSSL_free(buffer); if (r < 0) return r; } else if (hbtype == TLS1_HB_RESPONSE) { unsigned int seq; /* * We only send sequence numbers (2 bytes unsigned int), and 16 * random bytes, so we just try to read the sequence number */ n2s(pl, seq); if (payload == 18 && seq == s->tlsext_hb_seq) { s->tlsext_hb_seq++; s->tlsext_hb_pending = 0; } } return 0; } int tls1_heartbeat(SSL *s) { unsigned char *buf, *p; int ret = -1; unsigned int payload = 18; /* Sequence number + random bytes */ unsigned int padding = 16; /* Use minimum padding */ /* Only send if peer supports and accepts HB requests... */ if (!(s->tlsext_heartbeat & SSL_TLSEXT_HB_ENABLED) || s->tlsext_heartbeat & SSL_TLSEXT_HB_DONT_SEND_REQUESTS) { SSLerr(SSL_F_TLS1_HEARTBEAT, SSL_R_TLS_HEARTBEAT_PEER_DOESNT_ACCEPT); return -1; } /* ...and there is none in flight yet... */ if (s->tlsext_hb_pending) { SSLerr(SSL_F_TLS1_HEARTBEAT, SSL_R_TLS_HEARTBEAT_PENDING); return -1; } /* ...and no handshake in progress. */ if (SSL_in_init(s) || s->in_handshake) { SSLerr(SSL_F_TLS1_HEARTBEAT, SSL_R_UNEXPECTED_MESSAGE); return -1; } /* * Check if padding is too long, payload and padding must not exceed 2^14 * - 3 = 16381 bytes in total. */ OPENSSL_assert(payload + padding <= 16381); /*- * Create HeartBeat message, we just use a sequence number * as payload to distuingish different messages and add * some random stuff. * - Message Type, 1 byte * - Payload Length, 2 bytes (unsigned int) * - Payload, the sequence number (2 bytes uint) * - Payload, random bytes (16 bytes uint) * - Padding */ buf = OPENSSL_malloc(1 + 2 + payload + padding); if (buf == NULL) return -1; p = buf; /* Message Type */ *p++ = TLS1_HB_REQUEST; /* Payload length (18 bytes here) */ s2n(payload, p); /* Sequence number */ s2n(s->tlsext_hb_seq, p); /* 16 random bytes */ if (RAND_bytes(p, 16) <= 0) { SSLerr(SSL_F_TLS1_HEARTBEAT, ERR_R_INTERNAL_ERROR); goto err; } p += 16; /* Random padding */ if (RAND_bytes(p, padding) <= 0) { SSLerr(SSL_F_TLS1_HEARTBEAT, ERR_R_INTERNAL_ERROR); goto err; } ret = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buf, 3 + payload + padding); if (ret >= 0) { if (s->msg_callback) s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT, buf, 3 + payload + padding, s, s->msg_callback_arg); s->tlsext_hb_pending = 1; } err: OPENSSL_free(buf); return ret; } # endif # define MAX_SIGALGLEN (TLSEXT_hash_num * TLSEXT_signature_num * 2) typedef struct { size_t sigalgcnt; int sigalgs[MAX_SIGALGLEN]; } sig_cb_st; static int sig_cb(const char *elem, int len, void *arg) { sig_cb_st *sarg = arg; size_t i; char etmp[20], *p; int sig_alg, hash_alg; if (elem == NULL) return 0; if (sarg->sigalgcnt == MAX_SIGALGLEN) return 0; if (len > (int)(sizeof(etmp) - 1)) return 0; memcpy(etmp, elem, len); etmp[len] = 0; p = strchr(etmp, '+'); if (!p) return 0; *p = 0; p++; if (!*p) return 0; if (!strcmp(etmp, "RSA")) sig_alg = EVP_PKEY_RSA; else if (!strcmp(etmp, "DSA")) sig_alg = EVP_PKEY_DSA; else if (!strcmp(etmp, "ECDSA")) sig_alg = EVP_PKEY_EC; else return 0; hash_alg = OBJ_sn2nid(p); if (hash_alg == NID_undef) hash_alg = OBJ_ln2nid(p); if (hash_alg == NID_undef) return 0; for (i = 0; i < sarg->sigalgcnt; i += 2) { if (sarg->sigalgs[i] == sig_alg && sarg->sigalgs[i + 1] == hash_alg) return 0; } sarg->sigalgs[sarg->sigalgcnt++] = hash_alg; sarg->sigalgs[sarg->sigalgcnt++] = sig_alg; return 1; } /* * Set suppored signature algorithms based on a colon separated list of the * form sig+hash e.g. RSA+SHA512:DSA+SHA512 */ int tls1_set_sigalgs_list(CERT *c, const char *str, int client) { sig_cb_st sig; sig.sigalgcnt = 0; if (!CONF_parse_list(str, ':', 1, sig_cb, &sig)) return 0; if (c == NULL) return 1; return tls1_set_sigalgs(c, sig.sigalgs, sig.sigalgcnt, client); } int tls1_set_sigalgs(CERT *c, const int *psig_nids, size_t salglen, int client) { unsigned char *sigalgs, *sptr; int rhash, rsign; size_t i; if (salglen & 1) return 0; sigalgs = OPENSSL_malloc(salglen); if (sigalgs == NULL) return 0; for (i = 0, sptr = sigalgs; i < salglen; i += 2) { rhash = tls12_find_id(*psig_nids++, tls12_md, sizeof(tls12_md) / sizeof(tls12_lookup)); rsign = tls12_find_id(*psig_nids++, tls12_sig, sizeof(tls12_sig) / sizeof(tls12_lookup)); if (rhash == -1 || rsign == -1) goto err; *sptr++ = rhash; *sptr++ = rsign; } if (client) { if (c->client_sigalgs) OPENSSL_free(c->client_sigalgs); c->client_sigalgs = sigalgs; c->client_sigalgslen = salglen; } else { if (c->conf_sigalgs) OPENSSL_free(c->conf_sigalgs); c->conf_sigalgs = sigalgs; c->conf_sigalgslen = salglen; } return 1; err: OPENSSL_free(sigalgs); return 0; } static int tls1_check_sig_alg(CERT *c, X509 *x, int default_nid) { int sig_nid; size_t i; if (default_nid == -1) return 1; sig_nid = X509_get_signature_nid(x); if (default_nid) return sig_nid == default_nid ? 1 : 0; for (i = 0; i < c->shared_sigalgslen; i++) if (sig_nid == c->shared_sigalgs[i].signandhash_nid) return 1; return 0; } /* Check to see if a certificate issuer name matches list of CA names */ static int ssl_check_ca_name(STACK_OF(X509_NAME) *names, X509 *x) { X509_NAME *nm; int i; nm = X509_get_issuer_name(x); for (i = 0; i < sk_X509_NAME_num(names); i++) { if (!X509_NAME_cmp(nm, sk_X509_NAME_value(names, i))) return 1; } return 0; } /* * Check certificate chain is consistent with TLS extensions and is usable by * server. This servers two purposes: it allows users to check chains before * passing them to the server and it allows the server to check chains before * attempting to use them. */ /* Flags which need to be set for a certificate when stict mode not set */ # define CERT_PKEY_VALID_FLAGS \ (CERT_PKEY_EE_SIGNATURE|CERT_PKEY_EE_PARAM) /* Strict mode flags */ # define CERT_PKEY_STRICT_FLAGS \ (CERT_PKEY_VALID_FLAGS|CERT_PKEY_CA_SIGNATURE|CERT_PKEY_CA_PARAM \ | CERT_PKEY_ISSUER_NAME|CERT_PKEY_CERT_TYPE) int tls1_check_chain(SSL *s, X509 *x, EVP_PKEY *pk, STACK_OF(X509) *chain, int idx) { int i; int rv = 0; int check_flags = 0, strict_mode; CERT_PKEY *cpk = NULL; CERT *c = s->cert; unsigned int suiteb_flags = tls1_suiteb(s); /* idx == -1 means checking server chains */ if (idx != -1) { /* idx == -2 means checking client certificate chains */ if (idx == -2) { cpk = c->key; idx = cpk - c->pkeys; } else cpk = c->pkeys + idx; x = cpk->x509; pk = cpk->privatekey; chain = cpk->chain; strict_mode = c->cert_flags & SSL_CERT_FLAGS_CHECK_TLS_STRICT; /* If no cert or key, forget it */ if (!x || !pk) goto end; # ifdef OPENSSL_SSL_DEBUG_BROKEN_PROTOCOL /* Allow any certificate to pass test */ if (s->cert->cert_flags & SSL_CERT_FLAG_BROKEN_PROTOCOL) { rv = CERT_PKEY_STRICT_FLAGS | CERT_PKEY_EXPLICIT_SIGN | CERT_PKEY_VALID | CERT_PKEY_SIGN; cpk->valid_flags = rv; return rv; } # endif } else { if (!x || !pk) return 0; idx = ssl_cert_type(x, pk); if (idx == -1) return 0; cpk = c->pkeys + idx; if (c->cert_flags & SSL_CERT_FLAGS_CHECK_TLS_STRICT) check_flags = CERT_PKEY_STRICT_FLAGS; else check_flags = CERT_PKEY_VALID_FLAGS; strict_mode = 1; } if (suiteb_flags) { int ok; if (check_flags) check_flags |= CERT_PKEY_SUITEB; ok = X509_chain_check_suiteb(NULL, x, chain, suiteb_flags); if (ok == X509_V_OK) rv |= CERT_PKEY_SUITEB; else if (!check_flags) goto end; } /* * Check all signature algorithms are consistent with signature * algorithms extension if TLS 1.2 or later and strict mode. */ if (TLS1_get_version(s) >= TLS1_2_VERSION && strict_mode) { int default_nid; unsigned char rsign = 0; if (c->peer_sigalgs) default_nid = 0; /* If no sigalgs extension use defaults from RFC5246 */ else { switch (idx) { case SSL_PKEY_RSA_ENC: case SSL_PKEY_RSA_SIGN: case SSL_PKEY_DH_RSA: rsign = TLSEXT_signature_rsa; default_nid = NID_sha1WithRSAEncryption; break; case SSL_PKEY_DSA_SIGN: case SSL_PKEY_DH_DSA: rsign = TLSEXT_signature_dsa; default_nid = NID_dsaWithSHA1; break; case SSL_PKEY_ECC: rsign = TLSEXT_signature_ecdsa; default_nid = NID_ecdsa_with_SHA1; break; default: default_nid = -1; break; } } /* * If peer sent no signature algorithms extension and we have set * preferred signature algorithms check we support sha1. */ if (default_nid > 0 && c->conf_sigalgs) { size_t j; const unsigned char *p = c->conf_sigalgs; for (j = 0; j < c->conf_sigalgslen; j += 2, p += 2) { if (p[0] == TLSEXT_hash_sha1 && p[1] == rsign) break; } if (j == c->conf_sigalgslen) { if (check_flags) goto skip_sigs; else goto end; } } /* Check signature algorithm of each cert in chain */ if (!tls1_check_sig_alg(c, x, default_nid)) { if (!check_flags) goto end; } else rv |= CERT_PKEY_EE_SIGNATURE; rv |= CERT_PKEY_CA_SIGNATURE; for (i = 0; i < sk_X509_num(chain); i++) { if (!tls1_check_sig_alg(c, sk_X509_value(chain, i), default_nid)) { if (check_flags) { rv &= ~CERT_PKEY_CA_SIGNATURE; break; } else goto end; } } } /* Else not TLS 1.2, so mark EE and CA signing algorithms OK */ else if (check_flags) rv |= CERT_PKEY_EE_SIGNATURE | CERT_PKEY_CA_SIGNATURE; skip_sigs: /* Check cert parameters are consistent */ if (tls1_check_cert_param(s, x, check_flags ? 1 : 2)) rv |= CERT_PKEY_EE_PARAM; else if (!check_flags) goto end; if (!s->server) rv |= CERT_PKEY_CA_PARAM; /* In strict mode check rest of chain too */ else if (strict_mode) { rv |= CERT_PKEY_CA_PARAM; for (i = 0; i < sk_X509_num(chain); i++) { X509 *ca = sk_X509_value(chain, i); if (!tls1_check_cert_param(s, ca, 0)) { if (check_flags) { rv &= ~CERT_PKEY_CA_PARAM; break; } else goto end; } } } if (!s->server && strict_mode) { STACK_OF(X509_NAME) *ca_dn; int check_type = 0; switch (pk->type) { case EVP_PKEY_RSA: check_type = TLS_CT_RSA_SIGN; break; case EVP_PKEY_DSA: check_type = TLS_CT_DSS_SIGN; break; case EVP_PKEY_EC: check_type = TLS_CT_ECDSA_SIGN; break; case EVP_PKEY_DH: case EVP_PKEY_DHX: { int cert_type = X509_certificate_type(x, pk); if (cert_type & EVP_PKS_RSA) check_type = TLS_CT_RSA_FIXED_DH; if (cert_type & EVP_PKS_DSA) check_type = TLS_CT_DSS_FIXED_DH; } } if (check_type) { const unsigned char *ctypes; int ctypelen; if (c->ctypes) { ctypes = c->ctypes; ctypelen = (int)c->ctype_num; } else { ctypes = (unsigned char *)s->s3->tmp.ctype; ctypelen = s->s3->tmp.ctype_num; } for (i = 0; i < ctypelen; i++) { if (ctypes[i] == check_type) { rv |= CERT_PKEY_CERT_TYPE; break; } } if (!(rv & CERT_PKEY_CERT_TYPE) && !check_flags) goto end; } else rv |= CERT_PKEY_CERT_TYPE; ca_dn = s->s3->tmp.ca_names; if (!sk_X509_NAME_num(ca_dn)) rv |= CERT_PKEY_ISSUER_NAME; if (!(rv & CERT_PKEY_ISSUER_NAME)) { if (ssl_check_ca_name(ca_dn, x)) rv |= CERT_PKEY_ISSUER_NAME; } if (!(rv & CERT_PKEY_ISSUER_NAME)) { for (i = 0; i < sk_X509_num(chain); i++) { X509 *xtmp = sk_X509_value(chain, i); if (ssl_check_ca_name(ca_dn, xtmp)) { rv |= CERT_PKEY_ISSUER_NAME; break; } } } if (!check_flags && !(rv & CERT_PKEY_ISSUER_NAME)) goto end; } else rv |= CERT_PKEY_ISSUER_NAME | CERT_PKEY_CERT_TYPE; if (!check_flags || (rv & check_flags) == check_flags) rv |= CERT_PKEY_VALID; end: if (TLS1_get_version(s) >= TLS1_2_VERSION) { if (cpk->valid_flags & CERT_PKEY_EXPLICIT_SIGN) rv |= CERT_PKEY_EXPLICIT_SIGN | CERT_PKEY_SIGN; else if (cpk->digest) rv |= CERT_PKEY_SIGN; } else rv |= CERT_PKEY_SIGN | CERT_PKEY_EXPLICIT_SIGN; /* * When checking a CERT_PKEY structure all flags are irrelevant if the * chain is invalid. */ if (!check_flags) { if (rv & CERT_PKEY_VALID) cpk->valid_flags = rv; else { /* Preserve explicit sign flag, clear rest */ cpk->valid_flags &= CERT_PKEY_EXPLICIT_SIGN; return 0; } } return rv; } /* Set validity of certificates in an SSL structure */ void tls1_set_cert_validity(SSL *s) { tls1_check_chain(s, NULL, NULL, NULL, SSL_PKEY_RSA_ENC); tls1_check_chain(s, NULL, NULL, NULL, SSL_PKEY_RSA_SIGN); tls1_check_chain(s, NULL, NULL, NULL, SSL_PKEY_DSA_SIGN); tls1_check_chain(s, NULL, NULL, NULL, SSL_PKEY_DH_RSA); tls1_check_chain(s, NULL, NULL, NULL, SSL_PKEY_DH_DSA); tls1_check_chain(s, NULL, NULL, NULL, SSL_PKEY_ECC); } /* User level utiity function to check a chain is suitable */ int SSL_check_chain(SSL *s, X509 *x, EVP_PKEY *pk, STACK_OF(X509) *chain) { return tls1_check_chain(s, x, pk, chain, -1); }

 

//d1_both.c 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; if (s->msg_callback) s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT, &s->s3->rrec.data[0], s->s3->rrec.length, s, s->msg_callback_arg); if (hbtype == TLS1_HB_REQUEST) { 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; /* Enter response type, length and copy payload */ *bp++ = TLS1_HB_RESPONSE; s2n(payload, bp); memcpy(bp, pl, payload); bp += payload; /* Random padding */ RAND_pseudo_bytes(bp, padding); r = dtls1_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding); if (r >= 0 && s->msg_callback) s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding, s, s->msg_callback_arg); OPENSSL_free(buffer); if (r < 0) return r; } else if (hbtype == TLS1_HB_RESPONSE) { unsigned int seq; /* We only send sequence numbers (2 bytes unsigned int), * and 16 random bytes, so we just try to read the * sequence number */ n2s(pl, seq); if (payload == 18 && seq == s->tlsext_hb_seq) { dtls1_stop_timer(s); s->tlsext_hb_seq++; s->tlsext_hb_pending = 0; } } return 0; } int dtls1_heartbeat(SSL *s) { unsigned char *buf, *p; int ret; unsigned int payload = 18; /* Sequence number + random bytes */ unsigned int padding = 16; /* Use minimum padding */ /* Only send if peer supports and accepts HB requests... */ if (!(s->tlsext_heartbeat & SSL_TLSEXT_HB_ENABLED) || s->tlsext_heartbeat & SSL_TLSEXT_HB_DONT_SEND_REQUESTS) { SSLerr(SSL_F_DTLS1_HEARTBEAT,SSL_R_TLS_HEARTBEAT_PEER_DOESNT_ACCEPT); return -1; } /* ...and there is none in flight yet... */ if (s->tlsext_hb_pending) { SSLerr(SSL_F_DTLS1_HEARTBEAT,SSL_R_TLS_HEARTBEAT_PENDING); return -1; } /* ...and no handshake in progress. */ if (SSL_in_init(s) || s->in_handshake) { SSLerr(SSL_F_DTLS1_HEARTBEAT,SSL_R_UNEXPECTED_MESSAGE); return -1; } /* Check if padding is too long, payload and padding * must not exceed 2^14 - 3 = 16381 bytes in total. */ OPENSSL_assert(payload + padding <= 16381); /* Create HeartBeat message, we just use a sequence number * as payload to distuingish different messages and add * some random stuff. * - Message Type, 1 byte * - Payload Length, 2 bytes (unsigned int) * - Payload, the sequence number (2 bytes uint) * - Payload, random bytes (16 bytes uint) * - Padding */ buf = OPENSSL_malloc(1 + 2 + payload + padding); p = buf; /* Message Type */ *p++ = TLS1_HB_REQUEST; /* Payload length (18 bytes here) */ s2n(payload, p); /* Sequence number */ s2n(s->tlsext_hb_seq, p); /* 16 random bytes */ RAND_pseudo_bytes(p, 16); p += 16; /* Random padding */ RAND_pseudo_bytes(p, padding); ret = dtls1_write_bytes(s, TLS1_RT_HEARTBEAT, buf, 3 + payload + padding); if (ret >= 0) { if (s->msg_callback) s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT, buf, 3 + payload + padding, s, s->msg_callback_arg); dtls1_start_timer(s); s->tlsext_hb_pending = 1; } OPENSSL_free(buf); return ret; }

 


□ 자료 출처

1. "OpenSSL 취약점 하트블리드(HeartBleed), 왜 위험한가?", 이스트 시큐리티, https://blog.alyac.co.kr/76

2. "[CVE-2014-0160] OpenSSL 취약점 HeartBleed 실습 및 분석", https://jmoon.co.kr/164

3. "Heart Bleed Attack Research Paper", Safe Security, https://www.safe.security/resources/research-paper/heartbleed-attack/

4. "Exploiting CVE-2014-0160", http://marcoguerri.github.io/2014/08/heartbleed

 

'1-Day 취약점 분석' 카테고리의 다른 글

커널 파일시스템 LPE 취약점(CVE-2021-33909)  (0) 2021.08.11