Testing for Heartbleed vulnerability without exploiting the server.

Heartbleed is a serious vulnerability in OpenSSL that was disclosed on Tuesday, April 8th, and impacted any sites or services using OpenSSL 1.01 – 1.01.f and 1.0.2-beta1. Due to the nature of the bug, the only obvious way to test a server for the bug was an invasive attempt to retrieve memory–and this could lead to the compromise of sensitive data and/or potentially crash the service.

I developed a new test case that neither accesses sensitive data nor impacts service performance, and am posting the details here to help organizations conduct safe testing for Heartbleed vulnerabilities. While there is a higher chance of a false positive, this test should be safe to use against critical services.

The test works by observing a specification implementation error in vulnerable versions of OpenSSL: they respond to larger than allowed HeartbeatMessages.

Details:
OpenSSL was patched by commit 731f431. This patch addressed 2 implementation issues with the Heartbeat extension:

  1. HeartbeatRequest message specifying an erroneous payload length
  2. Total HeartbeatMessage length exceeding 2^14 (16,384 bytes)

Newer versions of OpenSSL silently discard messages which fall into the above categories. It is possible to detect older versions of OpenSSL by constructing a HeartbeatMessage and not sending padding bytes. This results in the below evaluating true:

/* Read type and payload length first */
if (1 + 2 + 16 > s->s3->rrec.length)
  return 0; /* silently discard */

Vulnerable versions of OpenSSL will respond to the request. However no server memory will be read because the client sent payload_length bytes.

False positives may occur when all the following conditions are met (but it is unlikely):

  1. The service uses a library other than OpenSSL
  2. The library supports the Heartbeat extension
  3. The service has Heartbeat enabled
  4. The library performs a fixed length padding check similar to OpenSSL

False negatives may occur when all the following conditions are met, and can be minimized by repeating the test:

  1. The service uses a vulnerable version of OpenSSL
  2. The Heartbeat request isn’t received by the testing client

I have modified the Metasploit openssl_heartbleed module to support the ‘check’ option.

You can download the updated module at
https://github.com/dchan/metasploit-framework/blob/master/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb

We hope you can use this to test your servers and make sure any vulnerable ones get fixed!

David Chan
Mozilla Security Engineer

7 responses

  1. Rick Flores wrote on :

    Has David Chan sent off an official PR to metasploit master?

    https://github.com/dchan/metasploit-framework/commit/6fafc10184bdef11ad7c22ba5fb2e33711d4d848

    1. David Chan wrote on :

      Hi Rick,

      I’ve sent off a pull request with more information on how the test works.
      https://github.com/rapid7/metasploit-framework/pull/3252

  2. Frank wrote on :

    Btw, this has a false positive with GnuTLS 3.2.0 and above. GnuTLS doesn’t check for any specific padding length, simply that the value in the payload length field is no larger than (message length – 3). Hence, your padding-less packets will be returned, like vulnerable openSSL.

    See source code below in lib/ext/heartbeat.c. hb_len is the length stored in the heartbeat length field, and len is the length of the heartbeat message as received. Other parts of the code have been removed for simplicity:

    int _gnutls_heartbeat_handle (gnutls_session_t session, mbuffer_st * bufel) {

    uint8_t *msg = _mbuffer_get_udata_ptr (bufel);
    size_t hb_len, len = _mbuffer_get_udata_size (bufel);

    pos = 0;
    type = msg[pos++];
    hb_len = _gnutls_read_uint16 (&msg[pos]);
    if (hb_len > len – 3)
    return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
    pos += 2;

    switch (type)
    {
    case HEARTBEAT_REQUEST:
    _gnutls_buffer_reset(&session->internals.hb_remote_data);
    ret = _gnutls_buffer_resize (&session->internals.hb_remote_data, hb_len);
    if (ret 0)
    memcpy(session->internals.hb_remote_data.data, &msg[pos], hb_len);

    session->internals.hb_remote_data.length = hb_len;
    return gnutls_assert_val(GNUTLS_E_HEARTBEAT_PING_RECEIVED);

    1. David Chan wrote on :

      GnuTLS was one of our our concerns for false positives. A zero length payload would be ideal since it will cause GnuTLS to return with an UNEXPECTED_PACKET_LENGTH a couple lines above the len – 3 check

      if (len < 4)
      return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET_LENGTH);

      Vulernable versions of OpenSSL would respond with a 19 byte packet, |type|len|padding|

      However we haven't been able to get the 0 length payload to work yet. The socket is closed before we can read the data.

  3. Olivier Mengué wrote on :

    It would also be useful to have some code that can test the local SSL library using the openssl API.

    Most local tests only check the version numbers reported by the API (which is just an API compatibility version and doesn’t tell much about the implementation) and versions numbers reported in opensslv.h at compile time, and that doesn’t work well with dynamically linked libraries: many false positives.
    Here is mine that has this flaw: https://gist.github.com/dolmen/10096474

  4. Jose wrote on :

    Como puedo saber si es o no vulnerable ? osea q http solo no lo es ?
    gracias

  5. Amit wrote on :

    Hi David,
    Do you consider using the extension in the link bellow to be safe? do you recommend using it for dealing with this security issue?
    https://addons.mozilla.org/en-US/firefox/addon/heartbleed_monitor/?src=api

    Thanks,
    — Amit.