TLS/SSL & the Heartbleed Attack

How the web encrypts traffic in transit — and how a single missing bounds check leaked the secrets of the internet.

What is TLS/SSL?

Transport Layer Security (TLS)

TLS (successor to SSL) is the protocol that puts the "S" in HTTPS. It provides three guarantees for data in transit:

PropertyWhat it meansHow TLS achieves it
Confidentiality Only sender & receiver can read the data Symmetric encryption (AES) with a session key
Integrity Data hasn't been tampered with HMAC / authenticated encryption (GCM)
Authentication You're really talking to who you think X.509 certificates + digital signatures
SSL vs TLS: SSL (Secure Sockets Layer) is the predecessor. SSL 3.0 was superseded by TLS 1.0 in 1999. All SSL versions are now considered insecure. When people say "SSL" today, they usually mean TLS. Modern browsers require at least TLS 1.2.

The TLS Handshake

Before any encrypted data flows, the client and server must agree on encryption parameters and establish a shared secret. This is the TLS handshake — it happens after the TCP handshake but before any HTTP data.

Key insight: The handshake uses asymmetric crypto (RSA/ECDHE) only to establish a shared session key. All subsequent data uses fast symmetric encryption (AES). This is why we study both!

HTTP vs HTTPS: What the Attacker Sees

A network eavesdropper (e.g., someone on the same Wi-Fi) can intercept packets. With plain HTTP, they see everything. With HTTPS (TLS), they see encrypted gibberish.

The Heartbeat Extension (RFC 6520)

Why Heartbeats Exist

TLS connections can go idle. The Heartbeat extension is a keep-alive mechanism: the client sends a small payload and asks the server to echo it back, proving the connection is still alive.

Heartbeat Request:
type: 1 (request)
payload_length: 5
payload: "HELLO"
padding: [16 random bytes]

The server reads the payload, copies payload_length bytes, and sends them back as a Heartbeat Response. Simple, right?

The critical assumption: The server trusts that payload_length matches the actual size of the payload data. If it doesn't verify this... bad things happen.

The Heartbleed Bug (CVE-2014-0160)

Disclosed in April 2014, Heartbleed was a buffer over-read vulnerability in OpenSSL's implementation of the Heartbeat extension. The bug existed for over two years before discovery and affected an estimated 17% of all SSL web servers (roughly 500,000 servers).

The Bug in One Sentence

OpenSSL's dtls1_process_heartbeat() copied payload_length bytes from memory without checking that the request actually contained that many bytes — a classic buffer over-read.

The Vulnerable Code (simplified)

// OpenSSL ssl/d1_both.c — the vulnerable function unsigned char *p = &s->s3->rrec.data[0], *pl; /* Read type and payload length from the request */ hbtype = *p++; n2s(p, payload); // payload = attacker-controlled length! pl = p; /* Allocate response buffer and copy payload back */ buffer = OPENSSL_malloc(1 + 2 + payload + padding); bp = buffer; *bp++ = TLS1_HB_RESPONSE; s2n(payload, bp); memcpy(bp, pl, payload); // BUG: copies 'payload' bytes // but never checks actual data length!
The fix was just one bounds check: verify that the actual record length is at least 1 + 2 + payload + 16 (type + length field + payload data + minimum padding). Without this check, an attacker can request up to 64 KB of server memory per heartbeat.

Interactive Heartbleed Simulation

Below is a simulated server's memory. The blue cells hold your heartbeat payload. Adjacent memory contains sensitive data (session keys, passwords, private key fragments) that the server is using for other connections.

Server Memory

Address 0x00 – 0x7F   (128 bytes shown, server has ~64 KB addressable per heartbeat)
Free / Empty Your Payload Sensitive Data (hover to peek) Leaked!

Send a Heartbeat Request

Actual payload size: 5 bytes

Heartbeat Request Packet

Send a heartbeat to see the packet...

Server Response

Waiting for heartbeat...

What Heartbleed Could Leak

Private Keys

The server's RSA/ECDSA private key — allowing the attacker to impersonate the server or decrypt past traffic.

Session Tokens

Cookies and session IDs of other users currently connected, enabling session hijacking.

Passwords

Plaintext credentials that were being processed in memory from recent login requests.

User Data

Emails, messages, API keys, or any data the server held in memory at the time.

No trace: Heartbleed exploitation left no log entries. Servers had no way to know if they had been attacked or what data was stolen. This is why mass key revocation and password resets were required.

Heartbleed Timeline

Dec 31, 2011

OpenSSL developer commits the Heartbeat extension code containing the bug. Code review misses the missing bounds check.

Mar 14, 2012

OpenSSL 1.0.1 released with the vulnerable code. Adopted rapidly by web servers, VPNs, and embedded devices worldwide.

~2 years of exposure

Every server running OpenSSL 1.0.1 through 1.0.1f with Heartbeat enabled was vulnerable. It is unknown if the bug was exploited during this period.

Apr 1, 2014

Independently discovered by Neel Mehta (Google Security) and Codenomicon researchers.

Apr 7, 2014

CVE-2014-0160 publicly disclosed. OpenSSL 1.0.1g released with the fix. Mass patching begins.

Aftermath

~500,000 servers affected. Certificate revocation/reissuance on massive scale. Prompted creation of the Core Infrastructure Initiative to fund critical open-source projects.

Apple's "goto fail" Bug (CVE-2014-1266)

Just five weeks before Heartbleed, Apple disclosed a devastating bug in its own TLS implementation (SecureTransport). A single duplicated line of code caused iOS and macOS to skip the server's signature verification entirely — accepting any certificate, even from an attacker.

The Bug in One Sentence

A duplicated goto fail; statement made the signature verification unconditionally jump to the success path — the server's certificate was never actually verified.

The Vulnerable Code

From Apple's sslKeyExchange.c. Read it carefully — can you spot the bug?

sslKeyExchange.c Apple SecureTransport

Execution Flow

Watch which code path runs — with and without the bug:

hashOut = SSLHashSHA1.update(hashCtx, &signedParams)
if ((err = SSLHashSHA1.final(...)) != 0) → goto fail
goto fail; ← duplicate!
fail: return err (err == 0 → success!)

What This Means in Practice

📱
iPhone / Mac
TLS handshake
🏦
Bank Server
Impact: Every iPhone, iPad, and Mac running iOS 7.0.0–7.0.5 or OS X 10.9.0–10.9.1 was vulnerable. An attacker on the same network (e.g., coffee shop Wi-Fi) could intercept any HTTPS connection — banking, email, iCloud — and the device would show a valid green lock. The fix was a one-line deletion.

Why Did This Happen?

No braces

The if statement didn't use {} braces, so the duplicate goto fail; always executed regardless of the condition above it.

Unreachable code

A compiler with -Wunreachable-code would have flagged the dead code after the second goto. This warning was not enabled.

Code review gap

The duplicate line likely came from a bad merge or copy-paste error. It went unnoticed through review because the surrounding code was repetitive.

No test coverage

There were no tests that presented an invalid certificate and verified rejection — tests only checked that valid connections succeeded.

The Logjam Attack (CVE-2015-4000)

Disclosed in May 2015, Logjam targets the Diffie-Hellman key exchange in TLS. Unlike Heartbleed (implementation bug) or goto fail (code error), Logjam exploits a protocol-level weakness: the ability to downgrade a connection to use 512-bit "export-grade" DH parameters that can be cracked in minutes.

The Attack in One Sentence

A man-in-the-middle modifies the TLS handshake to force the server to use 512-bit DHE_EXPORT keys instead of strong 2048-bit keys — then solves the discrete logarithm to recover the session key.

Why Key Size Matters

Diffie-Hellman security depends on the discrete logarithm problem being hard. But "hard" depends entirely on the size of the prime p. In the 1990s, US export regulations limited cryptography to 512-bit keys — deliberately weak so the NSA could break them.

512-bit EXPORT
768-bit
1024-bit
2048-bit SAFE
"Export-grade" crypto: During the 1990s US Crypto Wars, the government mandated that software exported outside the US could only use weak encryption (512-bit DH, 40-bit RC4). The regulations were lifted in 2000, but the DHE_EXPORT cipher suites remained in TLS for "backwards compatibility" — and servers kept supporting them for 15 more years.

How the Downgrade Works

The attacker sits between client and server and modifies the handshake in real time:

🖥
Client
ClientHello: DHE_RSA, ECDHE, AES-256...
Attacker changes to: DHE_EXPORT_RSA
ServerHello: OK, using DHE_EXPORT (512-bit p)
Attacker changes back to: DHE_RSA (hides downgrade)
→← Key exchange with 512-bit DH params
💀 Attacker solves discrete log → has session key!
🗄
Server

Cracking 512-bit DH

The Number Field Sieve algorithm can solve the discrete log for 512-bit primes. Critically, most of the computation (the precomputation phase) only depends on the prime p itself — not on any specific connection. Since nearly all export-grade servers used one of a handful of common primes, an attacker could precompute once and then crack any connection using that prime in about 70 seconds.

The Worse Finding: 1024-bit DH

The Logjam researchers estimated that a nation-state adversary (e.g., the NSA) could perform the precomputation for 1024-bit DH with a one-time investment of ~$100 million in custom hardware. This would allow passive decryption of any connection using the most common 1024-bit DH prime.

At the time of disclosure, the single most common 1024-bit prime (from Apache's httpd) was used by 18% of the Top 1 Million HTTPS sites.

This aligned disturbingly well with leaked NSA documents (Snowden, 2013) claiming the ability to break "vast amounts" of encrypted traffic. The Logjam researchers stated: "A close reading of published NSA leaks shows that the agency's attacks on VPNs are consistent with having achieved such a break."

Logjam vs Heartbleed vs goto fail

Heartbleedgoto failLogjam
CVE CVE-2014-0160 CVE-2014-1266 CVE-2015-4000
Bug type Buffer over-read (implementation) Logic error (implementation) Protocol downgrade + weak crypto
Root cause Missing bounds check in C Duplicate goto statement Export-grade cipher suites still enabled
What leaks Server memory (keys, passwords) Nothing — but MITM gets full access Session key → full traffic decryption
Attacker position Remote (anyone on internet) Man-in-the-middle (same network) Man-in-the-middle (same network)
Fix One bounds check Delete one line Disable export ciphers, use 2048+ bit DH
The lesson of Logjam: "Backwards compatibility" with deliberately weakened crypto is a ticking time bomb. Export-grade cipher suites were mandated by 1990s US policy, kept around for 15 years "just in case," and ultimately put millions of servers at risk. Technical debt from security policy decisions is the most dangerous kind.

Defenses & Lessons

DefenseHow it helps
Bounds checking The direct fix — verify that claimed payload length matches actual data. A single if statement prevented the bug.
Memory-safe languages Languages like Rust, Go, or Java prevent buffer over-reads at the language level. The bug was only possible because C allows unchecked memory access.
Perfect Forward Secrecy (PFS) With ECDHE key exchange, leaking the server's private key doesn't compromise past sessions — each session uses an ephemeral key.
Certificate Transparency Public logs of all issued certificates make it harder for an attacker to use a stolen private key to create fake certificates undetected.
ASLR Address Space Layout Randomization makes leaked memory harder to interpret — but Heartbleed still leaked enough plaintext to be devastating.
Always use braces The goto fail bug was only possible because if without {} makes it invisible that a second statement is unconditional. Enforcing braces in style guides eliminates this class of bug.
Compiler warnings -Wunreachable-code would have caught the dead code after the duplicate goto. Enable and treat all warnings as errors (-Werror).
Negative testing Both bugs survived because tests only checked the happy path. Tests must also verify that invalid inputs are rejected — present a bad certificate and assert failure.
Disable export ciphers The Logjam fix: remove DHE_EXPORT cipher suites entirely. TLS 1.3 solved this by eliminating cipher negotiation downgrades and requiring a minimum of 2048-bit DH.
Use ECDHE over DHE Elliptic-curve Diffie-Hellman is faster and not vulnerable to Logjam-style precomputation. A 256-bit ECDHE key is equivalent to ~3072-bit classic DH.
TLS 1.3 The latest TLS version (2018) removes all legacy cipher suites, eliminates the renegotiation/downgrade attack surface, and mandates PFS. It prevents Heartbleed, goto fail, and Logjam by design.
Takeaway: Heartbleed and goto fail were implementation bugs — TLS itself was sound. Logjam was different: it exploited a protocol-level weakness (export cipher suites) that had been known to be dangerous for years but was kept for "compatibility." The collective lesson: strong protocols need correct implementations, and we must aggressively deprecate weak crypto — not leave it around as a fallback.