HeartBleed: Inside the heart, what causes the bleeding?
Just after a few weeks since Apple's famous goto fail bug, there is one bug in OpenSSL which catches the attention from the world again. The bug is named HeartBleed, found in OpenSSL library, a famous open source library supporting lots of SSL/TLS communication among server/client applications.
The reason why this bug catches the attentions from the world is it affects almost all sites which are using the affected OpenSSL library, these includes many applications like Nginx server, some versions of Linux and many famous websites including Yahoo, Amazon. Private keys on the web server may be compromised by this bug, system administrators who rely on the affected OpenSSL to provide secure communication between server and client are in trouble now. They need to replace the affected version of OpenSSL with the fixed version and at the same time they need to clean the ass as well, i.e, they need to recreate private keys and revoke the existing keys and redistribute the generated keys. It's not a trivial task for them.
After knowing the background and its impact, you may want to know what causes the bug. Let's see below the analysis of the bug. Below is the code snippet which causes the bug:
unsigned int payload = 18; /* Sequence number + random bytes */ unsigned int padding = 16; /* Use minimum padding */ /* 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);
According to RFC 6520, the maximum size of a heartbeat request to 214 bytes (16KBytes), but OpenSSL itself generates far shorter requests. It's only around 40 bytes. After creating the heart beat request, the client will send the request to the server and wait for a reply.
The heartbeat request consisting of:
- The single byte 0x01 (denoting that this is a TLS1_HB_REQUEST).
- Two bytes containing the 16-bit representation of 34 (size of payload plus padding).
- Two bytes of payload consising of a 16-bit sequence number.
- 16 bytes of random data making up the rest of the 18-byte payload.
- 16 further random padding bytes, required by the standard.
After the server receives the request, it's going to process the data and create a response. However, the affected OpenSSL library is not so careful when processing the data.
Heartbeat replies are supposed to contain a copy of the payload data from the request, as a way of verifying that the encrypted circuit is still working both ways.
It turns out that you can send a small heartbeat request, but sneakily set your payload length field to 0xFFFF (65535 bytes). The payload length is not calculated from the actual payload data,instead it is sent by the client.
Once OpenSSL receives the length of 0xFFFF, it will directly copy the amount of data requested by the client by calling the memcpy() function below. even if the payload data from the client is far less than 0xFFFF. Below is the code snippet the server handles client request:
/* 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);
What may happen now is that the client may receive some data which is not supposed to be sent by the server. The server is experiencing data leak issue. And these leaked data may contain some garbage data, it may also contain important data like server's private key. As long as the private key is leaked, the server is compromised. All the encrypted data can be decrypted later.