Heartbleed - An OpenSSL Heartbeat Vulnerability
Heartbleed was a major buffer-over-read vulnerability in the OpenSSL implementation of the Heartbeat extension in TLS that went unnoticed for several years.
Heartbleed is the name given to the vulnerability found in the OpenSSL implementation of the heartbeat protocol. The name was coined by Codenomicon after their apparent discovery of the bug in 2014 and their registration of the domain heartbleed.com
to publicly disclose the bug. We later learned that Neel Mehta from Google discovered the bug less than a month earlier, patching their systems, and privately disclosing the bug to the OpenSSL team.
The vulnerability
Heartbleed is a buffer-over-read vulnerability allowing an attacker to read sensitive memory from a web server running OpenSSL using malformed heartbeat request messages. In addition, a server can extract the same type of information from a vulnerable client.
The attack is performed by sending a heartbeat request where the indicated payload_length
is much higher than the actual payload, resulting in the responder reading payload_length
bytes from the payload
buffer pointer. Since the payload_length
in higher than the length of the buffer, random memory found behind the payload
is included in the response. Since the payload_length
field is two bytes, the responder can respond with up to 2^{8*2}Bytes=64KB
of memory.
This memory could include parts of private keys, passwords and other sensitive data handled by OpenSSL. By sending multiple heartbeat requests, and adversary could for example reconstruct full private keys. A compromised key opens up the possibility for man-in-the-middle attacks. An even worse scenario is if compromised key is part of an intermediate-CA, allowing an adversary to sign their own certificates. We can assume that keys have been compromised, so old keys should be revoked.
Code analysis
Due to the fact that OpenSSL is open source, we can look at the git commits before[1] and after[2] the Heartbleed patch.
Before the patch
Looking at the file d1_both.c
, we can get a better understanding of the vulnerability. We will start of by looking at some relevant parts of the file:
/* Read type and payload length first */
hbtype = *p++;
n2s(p, payload);
pl = p;
In the code above, p
is currently pointing to our heartbeat message struct. hbtype
is assigned to the heartbeat type, n2s()
places the next 2 bytes from p in to payload
(which is in fact the length of the payload). pl
is then pointing to the rest of the payload data that the sender supplied.
A buffer is then allocated using OPENSSL_malloc
as seen below:
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;
Since the sender controls the value of payload
(the payload length), the butter can technically be set to 1 + 2 + 65535 + 16 = 6555
bits which is a little over 64 bytes. bp
is now the pointer to this buffer.
Finally, we do the memcpy()
as seen below:
/* Enter response type, length and copy payload */
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);
The heartbeat response code (1 byte) is added to the buffer, s2n()
adds the payload length to the buffer, and memcpy()
copies the payload starting at pl
using the user defined payload length payload
. A buffer-over-read is achieved as no bounds checks have been done against payload
.
Heartbleed patch
Patching the code was relatively simple:
/* 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;
The added if statements shown above, check for a zero-byte payload which it discards, and also bounds checking in regard to the indicated payload length being true.
Alternative fixes
In addition to the software fix, users could recompile the OpenSSL source code with the -DOPENSSL_NO_HEARTBEATS
option. This could be useful in scenarios where the patch is not available yet.
Further discussion
The OpenSSL code is completely open source. Anyone could have found and fixed the Heartbleed bug, however this bug went unnoticed for many years. This raises the question if being open-source is a 'good-enough' security safety-net when trying to write secure code.
The heartbeat protocol in itself as defined by the RFC is messy to implement in TLS due to the fact that neither the payload or payload length have any apparent use. The author of the OpenSSL heartbeat extention did however implement this useless functionality, increasing the potential attack surface.