Heartbleed: Anatomy of OpenSSL’s password, crypto-key leaking bug


High performance access to file storage

Analysis The OpenSSL bug dubbed Heartbleed is so bad, switching off the internet for a while sounds like a fantastic idea. A tiny flaw in the widely used encryption library allows anyone to trivially and secretly dip into vulnerable systems, from your bank’s HTTPS server to your private VPN, to steal passwords, login cookies, private crypto-keys and much more.

How, in 2014, is this possible?

A simple script for the exploit engine Metasploit can, in a matter of seconds, extract sensitive in-memory data from systems that rely on OpenSSL 1.0.1 to 1.0.1f for TLS encryption. The bug affects about 500,000, or 17.5 per cent, of trusted HTTPS websites, we’re told, as well as client software, email servers, chat services, and anything else using the aforementioned versions of OpenSSL.

A good number of popular web services have now been patched following disclosure of the vulnerability on Monday; you can use this tool to check (use at your own risk, of course), but don’t forget to do more than patch your OpenSSL installation if you’re affected – change your keys, dump your session cookies and evaluate your at-risk data.

The TLS heartbeat

The bug lies in OpenSSL’s implementation of the TLS heartbeat extension: it’s a keep-alive feature in which one end of the connection sends a payload of arbitrary data to other end, which sends back an exact copy of that data to prove everything’s OK. The heartbeat message, according to the official standard, looks like this, in C:

  HeartbeatMessageType type;
  uint16 payload_length;
  opaque payload[HeartbeatMessage.payload_length];
  opaque padding[padding_length]; 
} HeartbeatMessage;

This HeartbeatMessage arrives via an SSL3_RECORD structure, a basic building block of SSL/TLS communications. The key fields in SSL3_RECORD are given below; length is how many bytes are in the received HeartbeatMessage and data is a pointer to that HeartbeatMessage.

struct ssl3_record_st
  unsigned int length;    /* How many bytes available */
  unsigned char *data;    /* pointer to the record data */

So just to be clear, the SSL3 record’s data points to the start of the received HeartbeatMessage and length is the number of bytes in the received HeartbeatMessage. Meanwhile, inside the received HeartbeatMessage, payload_length is the number of bytes in the arbitrary payload that has to be sent back.

Whoever sends a HeartbeatMessage controls the payload_length but as we will see, this is never checked against the parent SSL3_RECORD‘s length field, allowing an attacker to overrun memory.

The diagram below shows how the attack works:

The Register illustration of the attack

Click to enlarge … note: this does not include the padding bytes

In the above example, an attacker sends a HeartbeatMessage with a single byte payload, as acknowledged by the SSL3’s length record, but lies in the payload_length field to claim the payload is 65535 bytes in size. The victim ignores the SSL3 record, and reads 65535 bytes from its own memory, starting from the received HeartbeatMessage payload, and copies it into a suitably sized buffer to send back to the attacker. It thus hoovers up far too many bytes, dangerously leaking information as indicated above in red.

Show me the code

The broken OpenSSL code that processes the incoming HeartbeatMessage looks like this, where p is a pointer to the start of the message:

/* Read type and payload length first */
hbtype = *p++;
n2s(p, payload);
pl = p;

So the message type is popped into the hbtype variable, the pointer is incremented by one byte, and the n2s() macro writes the 16-bit length of the heartbeat payload to the variable payload and increments the pointer by two bytes. Then pl becomes a pointer to the contents of the payload.

Let’s say a heartbeat message with a payload_length of 65535, ie: a heartbeat with a 64KB payload, the maximum possible, is received. The code has to send back a copy of the incoming HeartbeatMessage, so it allocates a buffer big enough to hold the 64KB payload plus one byte to store the message type, two bytes to store the payload length, and some padding bytes, as per the above structure.

It constructs the reply HeartbeatMessage structure with the following code, where bp is a pointer to the start of the reply HeartbeatMessage:

/* Enter response type, length and copy payload */
s2n(payload, bp);
memcpy(bp, pl, payload);

So the code writes the response type to the start of the buffer, increments the buffer pointer, uses the s2n() macro to write the 16-bit payload length to memory and increment the buffer pointer by two bytes, and then it copies payload number of bytes from the received payload into the outgoing payload for the reply.

Remember, payload is controlled by the attacker, and it’s quite large at 64KB. If the actual HeartbeatMessage sent by the attacker only has a payload of, say, one byte, and its payload_length is a lie, then the above memcpy() will read beyond the end of the received HeartbeatMessage and start reading from the victim process’s memory.

And this memory will contain other juicy information, such as passwords or decrypted messages from other clients.

In fact, it contains this sort of information, although we understand Yahoo! has since patched its systems:

The fix

The patch in OpenSSL 1.0.1g is essentially a bounds check, using the correct record length in the SSL3 structure (s3-rrec) that described the incoming HeartbeatMessage.

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;

OpenSSL’s implementation of TLS heartbeats was committed to the project’s source code 61 minutes to midnight on Saturday, 31 December, 2011. What we’re experiencing now is the mother of all delayed hangovers. ®

High performance access to file storage

Article source:


Comments are closed.