The Synopsys Software Integrity Group is now Black Duck®. Learn More

close search bar

Sorry, not available in this language yet

close language selection

How to cybersecurity: Heartbleed deep dive

Jonathan Knudsen

Oct 24, 2021 / 8 min read

Heartbleed is a serious vulnerability discovered in the openssl open source software component in April 2014.

This article is a deep dive on Heartbleed and its broader implications for application security:

  • Heartbleed is described in detail.
  • A proof-of-concept test environment is presented.
  • An exploit script is provided to extract user credentials from the test environment.
  • We’ll wrap up by describing how Heartbleed illustrates several important points about application security.

Heartbleed vulnerability in detail

Heartbleed is a vulnerability in an open source software component called openssl. As with many other open source software components, openssl is available for anyone in the world to use as part of an application (subject to its licensing, of course).

The openssl component is an implementation of the transport layer security (TLS) network protocol, which is used by many kinds of applications. TLS provides authentication, integrity, and confidentiality for network communication. For example, whenever you visit an https://… URL, your browser uses TLS to verify the server’s identity, set up an encrypted connection, and retrieve pages and other resources over the encrypted connection.

TLS is complicated and hard to get right. For application developers, it makes perfect sense to use an open source implementation such as openssl. As long as the license terms of the component are acceptable, using an open source component gives application developers a robust, mature implementation with very little initial expense.

The TLS protocol dictates how conversations happen between two network endpoints: a client and a server. You can think of the client as a web browser and the server as a web server. Fundamentally, any TLS conversation has two phases:

  1. The client and server greet each other and negotiate the cryptographic algorithms and parameters for the rest of the conversation. This phase of the conversation has messages that are exchanged in plaintext.
  2. Once negotiation is finished, the client and server enter the second phase in which they exchange encrypted data. This is the point of TLS: once the encryption has been set up, anyone snooping on the network will not be able to decipher the data exchanged between client and server.

The TLS protocol has gone through several major versions, and openssl has been updated to keep pace. Heartbleed was created when openssl was updated for TLS 1.2 in 2012.

Specifically, TLS 1.2 added a new type of message: the heartbeat. The intention was that either side could ask the other “Are you there?” A heartbeat request message from one side is acknowledged with a heartbeat response from the other side.

Heartbeat messages are very simple, consisting of a length field and a payload. The payload in the request is echoed back in the response.

For example, the following might be the content of a heartbeat request, shown as hexadecimal:

Length

Payload

0010

beef0000beef1111beef2222beef3333

The heartbeat response should contain exactly the same length and payload.

Unfortunately, the openssl developers made one critical error when implementing heartbeat messages. They fully trusted the length sent in the request. This is the Heartbleed vulnerability.

If the request lies about its payload length, openssl still returns the requested length, which includes stack memory. This type of vulnerability is “information leakage.” The data returned might contain sensitive information like recently used credentials or even the private cryptographic key of the server.

For example, a badly formed request could easily claim a length longer than the given payload:

Length

Payload

0020

beef0000beef1111beef2222beef3333

In this case, openssl might respond as follows:

Length

Payload

0020

beef0000beef1111beef2222beef3333bc62dc044600852376d7af18

The extra 16 (hex 10) bytes in the heartbeat response come directly from the server’s stack memory.

Even worse, the initial implementation of heartbeat messages allowed requests and responses in the unencrypted part of the TLS conversation, making exploitation very simple.

Because the payload length is 32 bits, an attacker could send heartbeat request messages with a false payload length of 65,535 bytes (64k, hex FFFF) and an empty payload, essentially prompting the victim system to dump out 64k chunks of memory. The attacker can examine these chunks of data for credentials, the server’s private key, and other sensitive information.

Using time travel to create a test bed

To see how Heartbleed works in practice, you just need a vulnerable server and an exploit script.

If you’d like to try this out for yourself, all the necessary scripts are here:

https://github.com/jknudsen-synopsys/heartbleed-box

The scripts work in a Linux environment with Docker installed. You can get the code as follows:

$ git clone https://github.com/jknudsen-synopsys/heartbleed-box 
$ cd heartbleed-box

To run a vulnerable server, you can retrieve an old version of openssl and build it yourself. I’m using a Docker container for this purpose; the relevant excerpt from the Dockerfile is as follows. Most notably, the git checkout command retrieves the 1.0.1f version, the last version vulnerable to Heartbleed.

RUN git clone https://github.com/openssl/openssl && \
    cd openssl && \
    git checkout OpenSSL_1_0_1f && \
    ./config && \
    make && \
    make install_sw

If you’re following along, you don’t need to run any of this manually. Just build the container like this:

$ ./build.sh

Be prepared to wait a minute or two.

If you inspect the Dockerfile, you’ll notice that it also runs a command to generate a key pair and certificate for a server. Finally, the ENTRYPOINT is configured to run openssl s_server, which implements a simple TLS-enabled web server.

Once you’ve built the container, you can run it as follows:

$ ./run.sh 
Using default temp DH parameters
Using default temp ECDH parameters
ACCEPT

The container starts up and runs the server, which is ready to accept incoming connections on port 4433.

Simulate login

In this example, we’ll exploit Heartbleed to retrieve user credentials. First, though, we need to simulate a user logging in to the server. One way this could happen in a web application is with a login form. This is often an HTML form whose input gets POSTed to the web application.

We can simulate submitting a login form using curl. Our example server, which is really openssl s_server, doesn’t actually have any pages. That doesn’t matter because we are just demonstrating how credentials that are submitted with a web form are available in the server memory to be poached by exploiting Heartbleed.

The following command, for example, simulates a login form that POSTs to /submit with an example username and password:

$ curl -k -s -m .1 -d "username=jknudsen&password=secret123"
https://localhost:4433/submit

The options are as follows:

  • -k ignores certificate validation, which is necessary because our server uses a simple self-signed certificate
  • -s suppresses output
  • -m is a timeout, which is needed because we’re submitting the form to an imaginary endpoint and don’t want to wait for a response
  • -d indicates that the supplied data will be POSTed

I’ve encapsulated this command in a script, so you can simulate a login like this:

In this example, we’ll exploit Heartbleed to retrieve user credentials. First, though, we need to simulate a user logging in to the server. One way this could happen in a web application is with a login form. This is often an HTML form whose input gets POSTed to the web application.

We can simulate submitting a login form using curl. Our example server, which is really openssl s_server, doesn’t actually have any pages. That doesn’t matter because we are just demonstrating how credentials that are submitted with a web form are available in the server memory to be poached by exploiting Heartbleed.

The following command, for example, simulates a login form that POSTs to /submit with an example username and password:

$ curl -k -s -m .1 -d "username=jknudsen&password=secret123"
https://localhost:4433/submit

The options are as follows:

  • -k ignores certificate validation, which is necessary because our server uses a simple self-signed certificate
  • -s suppresses output
  • -m is a timeout, which is needed because we’re submitting the form to an imaginary endpoint and don’t want to wait for a response
  • -d indicates that the supplied data will be POSTed

I’ve encapsulated this command in a script, so you can simulate a login like this:


$ ./simulate-post.sh jonathan secret123
Simulating login with username=jonathan and password=secret123

Keep in mind that these credentials are encrypted before being transmitted to the server.

Exploit Heartbleed

Now you will see how Heartbleed can be exploited to pluck the plaintext user credentials out of the server’s memory.

Because vulnerable versions of openssl respond to heartbeat messages in the unencrypted negotiation phase of the TLS conversations, exploitation is pretty simple.

First, we connect to the TCP port and send a valid TLS ClientHello message, which is enough to convince openssl that we are starting a TLS conversation. After that, we can send a malformed heartbeat request message, as previously described.

In Python, the ClientHello and heartbeat request messages can be defined as follows:

ClientHello and heartbeat request messages

Once the messages are defined, having a TLS conversation is pretty easy:

TLS conversation

The most complicated part of this exploit (and it’s not very complicated) is searching through the heartbeat response message to locate credentials. I won’t reproduce the code here, but in essence we just need to look for username= in the response and try to locate the end of that string.

I’ve wrapped all this up in exploit-01.py, which you can easily run as follows:

$ python3 exploit-01.py
[Connecting to localhost port 4433]
username=jonathan&password=secret123
[Closing socket]

The morals of the story

Seven years later, Heartbleed still has important lessons for all of us.
 

Know your code

First, managing open source software components is critically important for application security. While using open source components is a practical and fruitful strategy for application developers, those components do have to be managed properly. You have to know which components you’ve used in your applications, and you must be aware of any known vulnerabilities in those components. When new vulnerabilities are published about the software components you’ve used, you need to know right away so you can take action if necessary. (Likewise, you should know the software licenses of those components to ensure you are not using something improperly, but that is not the focus of this article.) A software composition analysis (SCA) solution like Black Duck SCA automates much of this work.
 

Security is everyone’s responsibility

Second, you cannot rely on anyone upstream for security. While open source components often provide high-quality implementations of functionality that your application needs, mistakes do happen, and open source developers might have a different idea of acceptable risk than you do. For the software components you use in your application, it is your responsibility to understand what kind of security testing has been done and decide if you need to augment that testing with your own evaluations. For critical components like openssl that are part of the attack surface of your applications, performing your own security testing is prudent. Depending on the type of component, security testing can include static analysis (Coverity, for example), software composition analysis (Black Duck SCA), interactive analysis (Seeker), and fuzzing (Defensics). It was Defensics fuzz testing, in fact, that uncovered Heartbleed.
 

Application security is critical in application development

Finally, application security is inseparable from application development. When you are designing new applications or new features of existing applications, you must harden your design by doing threat modeling to think about threats, exploits, and security controls. During development and testing, you must automate and integrate security testing tools so that developers fix security defects as they go. Deployment practices should follow the same rigor, with special attention given to cloud configurations, container base images, and application configuration. Finally, after release, newly discovered vulnerabilities, in either the application itself or its supply chain of open source components, must be promptly identified, evaluated, and addressed.

Continue Reading

Explore Topics