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:
| Property | What it means | How 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 |
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.
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.
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?
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)
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
Send a Heartbeat Request
Heartbeat Request Packet
Server Response
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.
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?
Execution Flow
Watch which code path runs — with and without the bug:
What This Means in Practice
Why Did This Happen?
The if statement didn't use {} braces, so the duplicate goto fail; always executed regardless of the condition above it.
A compiler with -Wunreachable-code would have flagged the dead code after the second goto. This warning was not enabled.
The duplicate line likely came from a bad merge or copy-paste error. It went unnoticed through review because the surrounding code was repetitive.
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.
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:
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
| Heartbleed | goto fail | Logjam | |
|---|---|---|---|
| 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 |
Defenses & Lessons
| Defense | How 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. |