SSL / TLS — The Complete Guide

Everything you need to know about SSL/TLS: how the handshake actually works (with a live 3D animation), every cipher suite explained, PKI internals, all the famous attacks (Heartbleed, POODLE, BEAST, DROWN), MitM scenarios, inspection tools, and a battle-tested cheat sheet — for pentesters and defenders.

From handshake internals to Heartbleed — every detail explained

What is SSL/TLS?

When you type https:// into your browser, you trigger one of the most important cryptographic protocols on the internet — TLS (Transport Layer Security). Without it, every login, every credit-card number, every private message would travel the network in plaintext for anyone to read.

TLS is the modern successor to SSL (Secure Sockets Layer). Although "SSL" survives in casual speech ("SSL certificate", "SSL handshake"), every actually-deployed version of SSL is deprecated — the protocol you're using is TLS.

What TLS gives you

PropertyWhat it means in practice
ConfidentialityEavesdroppers see only encrypted ciphertext, not your passwords or session cookies.
IntegrityIf anyone tampers with a packet in transit, both sides detect it and abort.
AuthenticationYou can prove the server is who it claims to be (via its X.509 certificate). Optionally the client too (mTLS).
Forward secrecyEven if the server's private key is later stolen, recorded past traffic stays unreadable (with modern cipher suites).

Postcard vs sealed envelope — the analogy

HTTP without TLS is like writing on the back of a postcard — every mail handler along the way can read it. HTTPS wraps that postcard in a tamper-evident sealed envelope addressed only to the intended recipient. Anyone can still touch it, but they see only the envelope, and any attempt to open it leaves a visible trace.

Where TLS is used (it's not just web)

ProtocolDefault PortPurpose
HTTPSPort 443Web traffic — by far the largest user.
SMTPSPort 465Email submission, encrypted.
IMAPSPort 993Encrypted IMAP mail retrieval.
POP3SPort 995Encrypted POP3.
FTPSPort 990FTP over TLS.
LDAPSPort 636Active Directory over TLS.
MQTTSPort 8883IoT / message broker traffic.
DoTPort 853DNS-over-TLS — encrypted DNS resolution.
XMPP-TLSPort 5223Encrypted instant-messaging.
OpenVPNPort 1194Uses TLS for control channel.

SSL vs TLS — A Brief History

TLS didn't spring into existence fully-formed. It evolved over nearly 30 years of broken versions, attacks, and patches. Knowing this timeline helps you understand why SSLv3 is poison and why TLS 1.3 looks the way it does.

VersionYearStandardised byCurrent Status
SSL 1.01994NetscapeNever released — too broken to ship.
SSL 2.01995NetscapeDeprecated 2011 (RFC 6176). Vulnerable to MitM downgrade.
SSL 3.01996NetscapeDeprecated 2015 (RFC 7568). POODLE killed it.
TLS 1.01999IETFDeprecated 2020. BEAST showed CBC weaknesses.
TLS 1.12006IETFDeprecated 2020. Mostly a TLS 1.0 fix.
TLS 1.22008IETF (RFC 5246)Still widely deployed. Safe with strong ciphers.
TLS 1.32018IETF (RFC 8446)Current best practice. Faster, simpler, safer.

Why "SSL" lingers

When the IETF took over the protocol from Netscape in 1999, they renamed it from SSL to TLS for political reasons (the IETF wanted ownership). The wire-format change from SSL 3.0 to TLS 1.0 was tiny. Vendors kept calling their products "SSL certificates" because customers already knew that name. Today, when someone says "SSL", they almost always mean TLS.

What's deprecated as of 2024

In production: only TLS 1.2 and TLS 1.3 should be enabled. SSLv2, SSLv3, TLS 1.0, and TLS 1.1 must be disabled on every server you operate. Most browsers refuse to connect over them anyway, but a misconfigured backend can still get hit.

How TLS Works — The Big Picture

Before we dive into byte-level handshake details, it helps to see the whole flow visually. The diagram below walks through every message of a TLS 1.2 RSA handshake — click play, or step through each phase.

Three phases of every TLS connection

PhaseWhat happens
1. NegotiateClient and server agree on the protocol version, cipher suite, and exchange random nonces.
2. Authenticate & Key-exchangeServer presents its certificate. Both sides derive a shared symmetric key (without anyone sending it in the clear).
3. Bulk encryptFrom now on, every record is encrypted + MAC'd with the symmetric session keys. Fast (symmetric crypto), forward-secret (with modern KEX).

Why both asymmetric and symmetric crypto?

Asymmetric crypto (RSA / ECDH) is slow but doesn't need a shared secret. Symmetric crypto (AES) is fast but both sides need the same key. TLS solves the chicken-and-egg by using asymmetric ops once at the start (the handshake) to negotiate a symmetric session key — then uses fast symmetric ops for the rest of the connection.

In TLS 1.3 the handshake takes just 1 round-trip (1-RTT) instead of 2 in TLS 1.2. The trick: client guesses the cipher and sends its key share immediately, so the server can respond with everything in one shot.

Inside the TLS Handshake — Step by Step

Now let's dissect each message on the wire. This is the TLS 1.2 RSA variant (still common in legacy systems and the easiest to learn). TLS 1.3 streamlines this — covered in §7.

Message 1: ClientHello

shell
Handshake Type: Client Hello (1)
Length: 215
Version: TLS 1.2 (0x0303)
Random: 5f3a...d2b4c1   (32 bytes — first 4 bytes = unix timestamp, rest = random)
Session ID: <empty> or <previous session id>
Cipher Suites Length: 32
Cipher Suites:
  TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
  TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
  TLS_RSA_WITH_AES_256_GCM_SHA384       (0x009d)
  ...
Compression Methods: null (0x00)
Extensions:
  server_name: example.com           (SNI — virtual hosting)
  supported_groups: x25519, secp256r1
  signature_algorithms: rsa_pss_rsae_sha256, ecdsa_secp256r1_sha256, ...
  application_layer_protocol_negotiation (ALPN): h2, http/1.1

Key field: SNI (Server Name Indication). Without it, hosting one IP for many TLS sites would be impossible — the server wouldn't know which certificate to present until after the handshake.

Message 2: ServerHello

shell
Handshake Type: Server Hello (2)
Version: TLS 1.2 (0x0303)
Random: 9c1d...8e7f3a   (32 bytes)
Session ID: 5a3b...8d2e (28 bytes — server may issue a new one for resumption)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
Compression Method: null
Extensions:
  renegotiation_info, application_layer_protocol_negotiation: h2

The server picks exactly one cipher suite from the client's list — usually the strongest both support. The client now knows: ECDHE for key exchange, RSA for authentication, AES-256-GCM for bulk, SHA-384 for the handshake PRF.

Message 3: Certificate

shell
Handshake Type: Certificate (11)
Certificate List:
  Certificate 1 (leaf):
    Subject: CN=example.com, O=Example Corp, C=US
    Issuer:  CN=DigiCert TLS RSA SHA256 2020 CA1
    Validity: 2024-01-01 to 2025-01-01
    Public Key: RSA 2048-bit
    SAN: DNS:example.com, DNS:www.example.com
    Signature Algorithm: sha256WithRSAEncryption
  Certificate 2 (intermediate):
    Subject: CN=DigiCert TLS RSA SHA256 2020 CA1
    Issuer:  CN=DigiCert Global Root CA
  Certificate 3 (root, usually omitted):
    pre-installed in client's trust store

The client now does:
1) Walks the chain back to a root it already trusts (in /etc/ssl/certs or the OS trust store)
2) Verifies every signature in the chain
3) Confirms the leaf cert's SAN/CN matches the hostname requested in SNI
4) Checks revocation (OCSP / CRL)

Message 4: ServerKeyExchange (ECDHE only)

shell
Handshake Type: Server Key Exchange (12)
Curve: x25519
Server Public Key: 3d8b...f72a   (32 bytes — server's ephemeral ECDH key share)
Signature Algorithm: rsa_pss_rsae_sha256
Signature: <RSA signature over (client_random || server_random || params)>
This message exists only when the cipher uses ephemeral DH/ECDH (DHE, ECDHE). The server signs its ephemeral public key with its long-term RSA/ECDSA private key. That signature is the only place the long-term key is used — which is exactly what gives us forward secrecy.

Message 5: ServerHelloDone

Just a marker: "I'm done sending; your turn."

Message 6: ClientKeyExchange

shell
RSA mode:
  EncryptedPreMasterSecret: <RSA-encrypted 48-byte premaster secret with server's public key>

ECDHE mode:
  ClientECDHKeyShare: <client's ephemeral ECDH public key, 32 bytes for x25519>

Both sides now derive the master secret from premaster_secret + client_random + server_random using the PRF. From the master secret they derive 4 keys: client_write_key, server_write_key, client_write_IV, server_write_IV.

Messages 7–10: ChangeCipherSpec + Finished (both sides)

shell
ChangeCipherSpec: 1 byte (0x01)
  "From here, my records are encrypted with the derived keys."

Finished: ENCRYPTED hash of the entire handshake transcript so far.
  If decryption + hash both verify, no attacker tampered with the negotiation.

Each side sends ChangeCipherSpec then Finished. If both Finished messages verify, the secure channel is open and application data flows.

Wireshark capture — what a real handshake looks like

bash
# Capture HTTPS traffic
sudo tcpdump -i eth0 -w tls.pcap port 443

# In Wireshark, filter to handshake messages:
tls.handshake

# To decrypt: set Edit > Preferences > Protocols > TLS
# Pre-Master-Secret log file: $SSLKEYLOGFILE
# (Modern Chrome/Firefox honor SSLKEYLOGFILE env var)
export SSLKEYLOGFILE=~/sslkeys.log

Interactive: ClientHello field-by-field

The visualisation below shows a real ClientHello as hex bytes — step through every named field (record header, handshake type, random nonce, cipher suites, SNI, signature algorithms…).

Cipher Suites Explained

A cipher suite is the bundle of algorithms used for one TLS session. In TLS 1.2 the suite name encodes four roles; TLS 1.3 reduced this dramatically.

Watch each segment of a real suite name light up:

Decoding a TLS 1.2 cipher suite name

shell
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    ^      ^   ^       ^      ^   ^
    |      |   |       |      |   +-- PRF / MAC hash
    |      |   |       |      +------ Mode of operation
    |      |   |       +------------- Symmetric bulk cipher
    |      |   +--------------------- Authentication algorithm
    |      +------------------------- Key-exchange algorithm
    +-------------------------------- Protocol family
ComponentExampleMeaning
KexECDHEElliptic Curve Diffie-Hellman Ephemeral — provides forward secrecy.
AuthRSAServer's certificate uses an RSA signing key.
CipherAES_256AES with a 256-bit key. AES_128 is also fine.
ModeGCMGalois/Counter Mode — AEAD (authenticated encryption with associated data). Built-in integrity, no separate MAC needed.
HashSHA384Hash used in the PRF, certificate verification, and Finished message hash.

TLS 1.3 cipher suite naming

shell
TLS_AES_256_GCM_SHA384
    ^      ^       ^   ^
    |      |       |   +-- Hash for HKDF
    |      |       +------ Mode
    |      +-------------- Cipher
    +--------------------- Family

Key-exchange and authentication are now negotiated separately via the supported_groups and signature_algorithms extensions, so the suite name only needs the bulk cipher + hash. Cleaner, less footgun-prone.

Strong vs weak — what to allow in 2024

VerdictSuiteWhy
✓ AllowTLS_AES_256_GCM_SHA384TLS 1.3, AEAD, strong
✓ AllowTLS_AES_128_GCM_SHA256TLS 1.3, AEAD, fast on hardware
✓ AllowTLS_CHACHA20_POLY1305_SHA256TLS 1.3, AEAD, fast in software
✓ AllowTLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384TLS 1.2, AEAD, forward-secret
✓ AllowTLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256TLS 1.2, AEAD, forward-secret
✗ Disable*_CBC_*CBC mode → Lucky 13, BEAST, padding-oracle attacks
✗ Forbid*_RC4_*RC4 biases — practically broken
✗ Forbid*_3DES_*SWEET32 — 64-bit block birthday attack
✗ ForbidTLS_RSA_*No forward secrecy — key compromise reveals all past sessions
✗ Forbid*_NULL_* / *_EXPORT_* / *_anon_*Test/legacy weakness ciphers

Public Key Infrastructure (PKI) & Certificates

TLS's authentication guarantee hinges on a sprawling system called PKI (Public Key Infrastructure). The short version: your browser trusts ~100 root Certificate Authorities (CAs), and any of them can vouch for any domain. This is both PKI's genius and its most criticised feature.

Anatomy of an X.509 certificate

bash
$ openssl x509 -in cert.pem -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 0e:64:c5:fb...
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Let's Encrypt, CN=R3
        Validity
            Not Before: Mar  1 00:00:00 2024 GMT
            Not After : May 30 23:59:59 2024 GMT
        Subject: CN=example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus: 00:b8:d3:...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:example.com, DNS:www.example.com, DNS:api.example.com
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            Authority Information Access:
                OCSP - URI:http://r3.o.lencr.org
    Signature Algorithm: sha256WithRSAEncryption
         <issuer's signature over the above>

Chain of trust

TierRole
Root CASelf-signed. Pre-installed in browsers / OS. ~100 of them. Long validity (20-30 years).
Intermediate CASigned by a root. Day-to-day signing happens here so root keys can stay offline.
Leaf (end-entity)Your domain's cert. Signed by an intermediate. Short validity (90 days for Let's Encrypt, 1 year for others).

Inspecting a real cert

bash
# Connect and print the cert chain
openssl s_client -showcerts -connect example.com:443 < /dev/null

# Just the leaf cert, with parsed fields
echo | openssl s_client -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -text

# Just the expiry date
echo | openssl s_client -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -dates

# Check if your cert is in CT logs
curl -s 'https://crt.sh/?q=example.com&output=json' | jq '.[].name_value'

OCSP, CRL, OCSP-stapling

A cert can be revoked before its expiry (key leak, etc.). The browser learns about revocations via:

MechanismHow it works
CRL (Certificate Revocation List)Big list of revoked serial numbers downloaded periodically. Slow, often stale.
OCSP (Online Cert Status Protocol)Browser asks CA "is this serial still valid?" in real time. Privacy leak: CA learns what sites you visit.
OCSP StaplingServer fetches the OCSP response and presents it during handshake. No client→CA traffic.

Let's Encrypt — free + automated PKI

bash
# Issue a cert via certbot
sudo certbot --nginx -d example.com -d www.example.com

# Renew (run twice daily via cron / systemd timer)
sudo certbot renew --quiet

Certificate Transparency (CT)

Every publicly-trusted cert issued since 2018 must be logged to multiple CT logs — append-only, cryptographically-auditable ledgers. This means rogue CAs can't silently issue certs for your domain: the cert appears in CT and you can be alerted. Tools like crt.sh and certspotter.com let you search.

Watch the chain of trust in action

The visualisation below walks through how a browser verifies a server's certificate — from the leaf back to a pre-installed root CA.

TLS 1.3 — What Changed and Why

TLS 1.3 (RFC 8446, 2018) is the biggest change in 20 years. Almost every dangerous footgun from TLS 1.2 was either fixed or removed entirely.

Visually, the biggest win is latency — see how the two handshakes race side-by-side:

What changed

FeatureDescription
1-RTT handshakeClient sends its key share in ClientHello. Server responds with everything (cert, key, Finished) in one shot. Cuts latency in half.
0-RTT resumptionFor repeat connections, client can send application data with the ClientHello. (Warning: replayable — only use for idempotent requests.)
Encrypted extensionsServer certificate is encrypted from message 2 onward. Passive eavesdroppers can't see which cert was used.
Only AEAD ciphersCBC mode is gone. Only AES-GCM, ChaCha20-Poly1305, AES-CCM remain.
Forward secrecy mandatoryRSA key exchange is removed. Every TLS 1.3 connection uses ephemeral DH.
Static handshake hashPRF replaced by HKDF + SHA-256/384. Cleaner key derivation.
RemovedCompression (CRIME fix), renegotiation, custom DHE groups, weak elliptic curves.

TLS 1.3 handshake in one picture

shell
Client                                          Server
======                                          ======
ClientHello                                  -->
    + key_share (e.g. x25519 pub)
    + signature_algorithms
    + supported_groups
    + cipher_suites: AES_GCM, CHACHA20...

                                             <-- ServerHello
                                                  + key_share (server's x25519 pub)
                                                  + selected cipher
                                             {EncryptedExtensions}
                                             {Certificate}
                                             {CertificateVerify}    (server signs handshake hash)
                                             {Finished}
                                             [Application Data]

{Finished}                                   -->
[Application Data]                           <-->

{} = encrypted with handshake-traffic keys
[] = encrypted with application-traffic keys
One round-trip. Cert is encrypted. Cipher suite + group are decided immediately. No fallback opportunities for downgrade attacks.

Watch the TLS 1.3 handshake animate

Same client + server endpoints as §3, but following the streamlined TLS 1.3 sequence — note how encryption kicks in starting at message 2 (notice the 🔒 padlock on the envelopes).

0-RTT and replay risk

0-RTT lets the client send application data with the first flight (using a pre-shared key from a previous session). This data is replayable — an attacker who recorded it can replay it indefinitely. Use 0-RTT only for GET-like idempotent requests, never for state-changing operations.

Common TLS Attacks — The Hall of Fame

Two decades of TLS attacks have hardened the protocol. Here are the famous ones — every pentester should recognise their fingerprints.

Heartbleed (CVE-2014-0160)

Year2014
AffectedOpenSSL 1.0.1 — 1.0.1f
SeverityCritical (CVSS 7.5) — disclosure of memory contents
CauseTLS Heartbeat extension: client sends payload + claimed length. Server didn't check that claimed length matched real length, returned that many bytes of memory.
LeakedPrivate keys, session cookies, passwords — anything in process memory.
shell
# Detect with nmap
nmap -p 443 --script ssl-heartbleed example.com

# Exploit (educational reference — patched everywhere now)
# https://github.com/musalbas/heartbleed-masstest

POODLE (CVE-2014-3566)

Year2014
AffectedSSL 3.0 (any cipher with CBC mode)
CauseSSLv3 didn't verify padding bytes. Adaptive chosen-plaintext attack against the CBC padding lets an attacker decrypt one byte at a time.
FixDisable SSLv3 globally. TLS_FALLBACK_SCSV signals client deliberately tried a lower version, server rejects.

BEAST (CVE-2011-3389)

AffectedTLS 1.0 + CBC ciphers
CauseIV for record N was the last ciphertext block of record N-1 — predictable. Attacker who can inject ciphertext into a session can recover plaintext.
FixTLS 1.1 randomized IVs. Or use TLS 1.2 + AEAD (GCM).

DROWN (CVE-2016-0800)

AffectedServers that still supported SSLv2 with the same key as a TLS server
CauseBleichenbacher-style attack against SSLv2 export ciphers. About 22% of HTTPS servers were vulnerable at disclosure.
FixDisable SSLv2 (and don't share keys across servers).

LOGJAM (CVE-2015-4000)

AffectedTLS clients accepting export-grade (512-bit) Diffie-Hellman
CauseActive attacker downgrades the DH group to 512-bit, then breaks it offline (a few hours with academic-scale hardware).
FixServer uses 2048+ bit DH groups. Disable export ciphers. Use ECDHE instead of plain DHE.

SWEET32 (CVE-2016-2183)

64-bit block ciphers (3DES, Blowfish) hit birthday collisions after ~32 GB. An attacker who can keep a long-lived HTTPS session active and inject known plaintext can recover cookies. Fix: disable all 64-bit block ciphers.

CRIME and BREACH

AttackHowMitigation
CRIME (2012)TLS-level compression leaked size info → attacker recovers session cookies by chosen-plaintext expansion ratios.Fix: disable TLS compression (already disabled by default in modern stacks).
BREACH (2013)HTTP-level (gzip) compression of responses that include reflected user input + a secret allows recovery.Fix: don't reflect user input in compressed responses, disable gzip on sensitive pages, or use length-randomising padding.

Lucky 13 (CVE-2013-0169)

Timing side-channel against CBC + HMAC: the time to verify a record depends on whether the padding was valid. With enough samples, an attacker recovers plaintext. AEAD ciphers (GCM, ChaCha20-Poly1305) are immune.

Renegotiation attacks (CVE-2009-3555)

Attacker prepends data to a victim's HTTPS session by performing a separate handshake first and then injecting a renegotiation. Fixed by RFC 5746 (Secure Renegotiation extension) — disabled in TLS 1.3 entirely.

Quick reference: vulnerability vs cipher matrix

AttackRoot causeMitigation status today
HeartbleedOpenSSL bugPatched
POODLESSLv3 paddingSSLv3 disabled
BEASTTLS 1.0 + CBCUse TLS 1.2+ AEAD
DROWNSSLv2 sharing keySSLv2 disabled
LOGJAMExport DH downgradeDisable export ciphers
SWEET3264-bit block ciphersDisable 3DES/Blowfish
CRIMETLS compressionCompression disabled
BREACHHTTP gzipApp-level care needed
Lucky 13CBC timingUse AEAD ciphers
RenegTLS 1.0/1.1RFC 5746 / TLS 1.3

Man-in-the-Middle Scenarios

TLS is meant to defeat the man-in-the-middle. But misconfigurations and human factors still create attack windows. Watch what changes when you switch from plain HTTP to HTTPS:

Scenario 1: SSL Strip

bash
# Moxie Marlinspike's classic — strip HTTPS from HTTP redirects
# Attacker on the LAN, ARP-poisons victim's gateway:
sudo arpspoof -i eth0 -t 192.168.1.10 192.168.1.1

# Run sslstrip to rewrite https:// links to http://
sudo sslstrip -l 8080 &
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080

Defence: HSTS preload. Browsers refuse plain HTTP for sites on the HSTS list — even if the user types example.com, the request is upgraded to HTTPS before any DNS lookup.

Scenario 2: Burp Suite intercepting your own traffic

You install Burp's CA cert in your browser/device trust store. Burp generates a leaf cert for each visited domain, signed by its own CA. Your browser trusts it. Burp can now see and modify every TLS request.

shell
# In Burp: Proxy > Options > Import / export CA certificate
# Save as portswigger-cacert.der
# Install on Android (rooted) or browser trust store

# For mobile apps that pin certs:
#  - Use Frida + universal SSL unpinning script:
#    https://codeshare.frida.re/@masbog/frida-ios-ssl-unpinning/
#  - Or Objection: objection --gadget com.target.app explore

Scenario 3: Bypass certificate pinning

Pinning hard-codes which CA (or specific public key) is acceptable for an app. Defeats Burp-style MitM. Bypass approaches:

TechniqueHow
Frida unpinningRuntime hook of certificate validation methods. Works on Android/iOS. Universal scripts exist.
Patch the APKDecompile with apktool, replace pinning logic with always-true, rebuild + sign.
Trust user CAs (older Android)Pre Android 7, user-installed CAs were trusted by all apps. Modern apps must explicitly opt-in via network_security_config.xml.
xposed / LSPosed moduleSSLUnpinning xposed module on rooted devices.

Scenario 4: Rogue CA / nation-state

If an attacker controls (or compels) any CA in the browser's trust store, they can mint a valid cert for any domain. Real-world cases:

IncidentWhat happened
DigiNotar (2011)Compromised; issued ~500 rogue certs including google.com. Used to MitM Iranian Gmail users. CA removed from all trust stores → company went bankrupt within weeks.
TURKTRUST (2012)Issued intermediate CA certs by accident — used to MitM Google.
WoSign / StartCom (2016)Backdated certs to avoid SHA-1 deprecation. Untrusted by all major browsers.
Symantec (2017)Many improperly-issued certs. Google distrusted Symantec roots in phases.
Mitigation today: Certificate Transparency means any cert issued for your domain is publicly logged. Tools like certspotter or crt.sh alert you to issuance you didn't authorise.

Inspecting TLS — Tools & Techniques

Every TLS pentester needs these. Memorize them.

openssl s_client — the swiss army knife

bash
# Basic connect, see cert + handshake
openssl s_client -connect example.com:443

# With SNI (required for vhosts)
openssl s_client -connect 1.2.3.4:443 -servername example.com

# Force a specific protocol version
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
openssl s_client -connect example.com:443 -ssl3      # to confirm SSLv3 is off

# Force a specific cipher
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES256-GCM-SHA384'

# Dump just the leaf cert
openssl s_client -connect example.com:443 < /dev/null 2>/dev/null \
  | openssl x509 -noout -text

# Check OCSP stapling
openssl s_client -connect example.com:443 -status < /dev/null 2>/dev/null \
  | grep -A 17 'OCSP response:'

testssl.sh — comprehensive scanner

bash
# One-line install
git clone --depth 1 https://github.com/drwetter/testssl.sh.git
cd testssl.sh

# Full scan
./testssl.sh example.com

# Just protocol + cipher checks
./testssl.sh -p -E example.com

# All known vulnerabilities at once
./testssl.sh -U example.com

# Output JSON for tooling
./testssl.sh --jsonfile out.json example.com

nmap NSE TLS scripts

shell
# Enumerate ciphers
nmap --script ssl-enum-ciphers -p 443 example.com

# Check Heartbleed
nmap --script ssl-heartbleed -p 443 example.com

# Check for known weak certs (Debian-OpenSSL bug, ROBOT, etc.)
nmap --script ssl-known-key -p 443 example.com
nmap --script tls-poodle -p 443 example.com
nmap --script tls-ticketbleed -p 443 example.com

sslyze — Python-native scanner

shell
pip install sslyze

# Full scan against example.com:443
sslyze --regular example.com

# Specific checks
sslyze --certinfo --reneg --heartbleed --robot example.com

Wireshark — decrypting TLS traffic

bash
# Method 1: SSLKEYLOGFILE (works for Chrome/Firefox/curl built with NSS)
export SSLKEYLOGFILE=~/sslkeys.log
chromium &
# Then in Wireshark: Preferences > Protocols > TLS > (Pre)-Master-Secret log filename

# Method 2: server's private key (RSA only, no forward secrecy)
# Preferences > TLS > RSA keys list
# Add entry: 1.2.3.4, 443, http, /path/to/server.key

# Method 3: live decryption with Burp
# Set browser's HTTP/HTTPS proxy to Burp (127.0.0.1:8080)

SSL Labs (online — by Qualys)

ssllabs.com/ssltest grades any public HTTPS server A+ → F. The defacto standard for TLS health checks. Run it on your own servers; aim for A+ (HSTS + strong ciphers + OCSP-stapling).

Real-World Attack Workflow

A pentester's typical TLS workflow against a target. This is what you'd run on an external engagement.

Step 1: Discover TLS endpoints

bash
# All TLS-enabled ports on the target
nmap -p- --open --script ssl-enum-ciphers target.com

# Or limit to common TLS ports
nmap -p 443,8443,465,993,995,636,5061,5223,5269,5671 --script ssl-cert target.com

# Pull all subdomains from CT logs
curl -s 'https://crt.sh/?q=%25.target.com&output=json' \
  | jq -r '.[].name_value' | sort -u

Step 2: Cert recon — find more attack surface

Subdomains, IP addresses, and internal hostnames often leak in SAN fields.

shell
# Pull every SAN from a cert
openssl s_client -connect target.com:443 < /dev/null 2>/dev/null \
  | openssl x509 -noout -ext subjectAltName

# Or use censys.io / shodan to find every cert with the org name
shodan search 'ssl.cert.subject.O:"Target Corp"'

Step 3: Profile each endpoint

shell
# Versions and ciphers
nmap --script ssl-enum-ciphers -p 443 target.com

# Famous vulns
./testssl.sh --severity HIGH --jsonfile target.json target.com:443

# Look for these red flags in output:
#  - SSLv2 / SSLv3 enabled
#  - TLS 1.0 / 1.1 enabled
#  - CBC ciphers offered
#  - 3DES / RC4 / NULL ciphers
#  - Self-signed or expired certs
#  - Weak DH groups (< 2048)
#  - Heartbleed, ROBOT, etc.

Step 4: If a vuln is found — exploit safely

Always confirm scope in your rules of engagement before running active exploits. Some of these (Heartbleed dumps, MitM via ARP spoofing) can crash production or expose unrelated user data.

Step 5: Report

A good TLS finding includes:

  • Hostname + port + IP
  • Affected protocol version / cipher / extension
  • CVE / CWE reference
  • Proof — testssl.sh or sslyze output snippet
  • Concrete remediation: server config snippet, package version to upgrade, etc.

Mitigation & Best Practices

Hardening checklist for any production HTTPS server.

1. Protocol versions

shell
# nginx
ssl_protocols TLSv1.2 TLSv1.3;

# Apache
SSLProtocol -all +TLSv1.2 +TLSv1.3

# HAProxy
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11

2. Cipher list — Mozilla intermediate (2024)

shell
# nginx
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;

# Use Mozilla's generator: https://ssl-config.mozilla.org/

3. HSTS — never serve plaintext again

shell
# Tells browser to ALWAYS use HTTPS for this site + subdomains for 1 year
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# Submit to https://hstspreload.org/ so even first connection is forced HTTPS

4. OCSP stapling

shell
# nginx
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;

5. Certificate hygiene

  • Use ECDSA (P-256 or P-384) where possible — smaller, faster than RSA
  • RSA: 2048-bit minimum; 4096-bit is overkill for TLS
  • Automated renewal (certbot + cron / systemd timer)
  • Monitor expiry: add Prometheus blackbox exporter or use uptime-kuma
  • CAA DNS record to lock down which CAs may issue for your domain
shell
# CAA record - only Let's Encrypt may issue
example.com.    IN  CAA 0 issue "letsencrypt.org"
example.com.    IN  CAA 0 issuewild "letsencrypt.org"
example.com.    IN  CAA 0 iodef "mailto:[email protected]"

6. DH parameters

shell
# Generate strong DH params (one-time, save and reuse)
openssl dhparam -out /etc/ssl/dhparam.pem 4096

# nginx
ssl_dhparam /etc/ssl/dhparam.pem;

7. Disable session tickets if you don't need them

Session tickets compromise forward secrecy if the ticket-key is leaked. Either rotate ticket keys frequently (hourly) or disable them and rely on session IDs.

shell
# nginx
ssl_session_tickets off;

8. Verify your config grades A+

shell
# After deploying changes:
./testssl.sh example.com

# Or check at https://www.ssllabs.com/ssltest/analyze.html?d=example.com

TLS vs Other Secure Protocols

TLS isn't the only secure-transport game in town. Knowing where each one fits helps you pick the right tool.

ProtocolLayer / TypeBest For
TLSApplication-layer security over TCPWeb, mail, most app traffic. Per-connection. Cert-based auth.
DTLSTLS for UDP (Datagram TLS)VoIP, WebRTC, QUIC predecessor, IoT. Same crypto as TLS but tolerates packet loss.
QUICNew transport replacing TCP, with TLS 1.3 baked inHTTP/3. 1-RTT or 0-RTT connection establishment. Always encrypted.
SSHCustom protocol, not based on TLSRemote shell, port forwarding, file transfer. Mutual auth via keys.
IPsecNetwork-layer (Layer 3) encryptionSite-to-site VPNs, host-to-host. Encrypts ALL traffic between two endpoints.
WireGuardModern simple VPN, kernel-levelPersonal + corporate VPNs. ChaCha20-Poly1305, Curve25519, ~4000 LoC.
Noise ProtocolCryptographic frameworkUsed inside WireGuard, Lightning Network. Designed for forward-secret embedded use.

TLS vs SSH — they're not competitors

SSH is for interactive admin: one user, one shell. TLS is for application data: any client, any server, often anonymous. SSH uses its own crypto and key types (RSA/ECDSA/Ed25519 keys, no certificates by default). TLS leans on PKI / X.509 for identity. Both use roughly the same algorithms underneath (ECDH, AES-GCM, etc.).

Quick Reference Cheat Sheet

One-liners and constants that come up constantly.

Common openssl one-liners

shell
# Generate RSA key + CSR for new cert
openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr

# Generate ECDSA key + CSR
openssl ecparam -name prime256v1 -genkey -noout -out server.key
openssl req -new -key server.key -out server.csr

# Self-signed cert (for testing only!)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout self.key -out self.crt -subj '/CN=localhost'

# Check cert/key match (modulus should match)
openssl x509 -noout -modulus -in cert.pem | openssl md5
openssl rsa  -noout -modulus -in key.pem  | openssl md5

# Convert formats
openssl x509 -in cert.crt -out cert.pem -outform PEM           # DER → PEM
openssl pkcs12 -in cert.pfx -out cert.pem -nodes               # PFX → PEM
openssl pkcs12 -export -in cert.pem -inkey key.pem -out out.pfx # PEM → PFX

# Verify a cert chain
openssl verify -CAfile chain.pem cert.pem

# Show expiry date only
openssl x509 -in cert.pem -noout -enddate

# Get the SHA-256 fingerprint
openssl x509 -in cert.pem -noout -fingerprint -sha256

Common TLS ports

PortService
443HTTPS
465SMTPS (submission)
587SMTP STARTTLS
993IMAPS
995POP3S
990FTPS (implicit)
21FTP STARTTLS (explicit)
636LDAPS
853DoT (DNS-over-TLS)
5061SIP-TLS
5223XMPP-TLS
8883MQTTS

Quick risk-rating of what you might see in the wild

GradeWhat that usually means
A+TLS 1.3 only, ECDHE, AEAD, HSTS preload, OCSP-stapling
ATLS 1.2+1.3, ECDHE, AEAD, HSTS
BTLS 1.2+, some non-AEAD ciphers
CTLS 1.0/1.1 enabled, or no forward secrecy
FSSLv3 / weak ciphers / expired cert

Tool one-liners you'll use weekly

bash
# Quickly see TLS version + cipher of a site
curl -v https://example.com 2>&1 | grep 'SSL connection'

# Check expiry of every cert in a directory
for f in *.pem; do echo -n "$f: "; openssl x509 -enddate -noout -in "$f"; done

# Test if cert was issued for a specific name
openssl x509 -in cert.pem -noout -ext subjectAltName

# Test STARTTLS on SMTP
openssl s_client -starttls smtp -connect smtp.example.com:587

# All ciphers a server offers
nmap --script ssl-enum-ciphers -p 443 example.com

Closing Thoughts

You now know more about TLS than 99% of people who deploy it. The protocol is huge and has 30 years of history, but every part of it boils down to: negotiate, authenticate, key-exchange, encrypt. Every famous attack abused one of these four phases — and every modern best practice is about making one of them harder to break.

Build a habit: every time you deploy a service, run testssl.sh against it. Every time you build a feature that handles tokens or sessions, ask yourself what an attacker on the same network could see. TLS is the foundation — set it up right, and most of the network-level attack surface disappears.

Reactions

Related Articles