SSL/TLS Misconfiguration: Code Review Guide
Table of Contents
1. Introduction to SSL/TLS Misconfiguration
SSL/TLS (Secure Sockets Layer / Transport Layer Security) is the encryption protocol that protects data in transit between clients and servers. Every HTTPS connection relies on TLS to provide confidentiality, integrity, and authentication. When TLS is misconfigured, attackers can intercept, read, and modify all traffic — including credentials, session tokens, personal data, and API calls.
Why This Matters
TLS misconfigurations are consistently ranked in the OWASP Top 10 (under "Cryptographic Failures" and "Security Misconfiguration"). They're especially dangerous because they're invisible to end users — the browser may show a padlock icon while the underlying TLS configuration is critically weak. During code review, TLS issues appear in server configurations, application code that makes HTTPS requests, and missing security headers.
In this guide, you'll learn how TLS works and where the handshake can be compromised, which protocol versions and cipher suites are vulnerable, how certificate misconfigurations enable MITM attacks, why HSTS is critical for preventing HTTP downgrade attacks, how to identify TLS issues in code, configurations, and dependencies, and how to harden server TLS configurations.
TLS Handshake & Common Attack Points
TLS 1.3 Handshake (Simplified)
Where Misconfigurations Enable Attacks
What is the primary security goal that TLS achieves for web traffic?
2. TLS Fundamentals
Understanding TLS versions and the handshake process is essential for identifying misconfigurations. Here is the current landscape.
TLS/SSL Protocol Version Status
| Protocol | Year | Status | Known Vulnerabilities |
|---|---|---|---|
| SSL 2.0 | 1995 | ❌ Deprecated — MUST NOT use | DROWN, no integrity protection, insecure MAC |
| SSL 3.0 | 1996 | ❌ Deprecated — MUST NOT use | POODLE, CBC padding oracle, no AEAD |
| TLS 1.0 | 1999 | ❌ Deprecated — should disable | BEAST, Lucky13, weak CBC, no AEAD requirement |
| TLS 1.1 | 2006 | ❌ Deprecated — should disable | No known critical flaws but lacks modern ciphers, no AEAD requirement |
| TLS 1.2 | 2008 | ✅ Acceptable — widely supported | Secure when configured correctly (AEAD ciphers, no CBC, no RC4) |
| TLS 1.3 | 2018 | ✅ Recommended — strongest | Removed all weak ciphers, only AEAD, 1-RTT handshake, no renegotiation |
TLS 1.3 simplifications: TLS 1.3 removed support for all weak cryptographic primitives. There are no RSA key exchanges (only ephemeral Diffie-Hellman for forward secrecy), no CBC mode ciphers (only AEAD), no compression (prevents CRIME/BREACH), no renegotiation (prevents renegotiation attacks), and only 5 cipher suites (all strong). This means TLS 1.3 is very hard to misconfigure — most issues arise from supporting older TLS versions alongside it.
TLS 1.3 cipher suites (all secure)
1# TLS 1.3 only allows these 5 cipher suites — all AEAD:
2TLS_AES_256_GCM_SHA384 # AES-256 in GCM mode
3TLS_AES_128_GCM_SHA256 # AES-128 in GCM mode
4TLS_CHACHA20_POLY1305_SHA256 # ChaCha20 stream cipher (great on mobile)
5TLS_AES_128_CCM_SHA256 # AES-128 in CCM mode
6TLS_AES_128_CCM_8_SHA256 # AES-128 in CCM mode (8-byte tag)
7
8# Compare with TLS 1.2 which supports 300+ cipher suites,
9# many of which are weak or broken.Forward Secrecy
Forward secrecy (PFS) ensures that compromising the server's private key does not allow decryption of past recorded traffic. TLS 1.3 mandates forward secrecy via ephemeral Diffie-Hellman key exchange. In TLS 1.2, forward secrecy is only available with ECDHE or DHE cipher suites — static RSA key exchange (e.g., TLS_RSA_WITH_AES_256_CBC_SHA) does NOT provide forward secrecy. During code review, flag any TLS 1.2 configuration that includes non-ECDHE/DHE cipher suites.
3. Protocol Version Vulnerabilities
Supporting outdated TLS/SSL versions exposes the application to well-known attacks. A protocol downgrade attack forces the connection to use an older, vulnerable version even when both client and server support newer versions.
Protocol Version Attacks
| Attack | Affected Protocol | Impact | CVE |
|---|---|---|---|
| POODLE | SSL 3.0 | Decrypt HTTPS traffic byte-by-byte via CBC padding oracle | CVE-2014-3566 |
| BEAST | TLS 1.0 | Decrypt HTTPS traffic via predictable CBC IV | CVE-2011-3389 |
| DROWN | SSL 2.0 | Decrypt TLS traffic if server also supports SSLv2 (even on a different port) | CVE-2016-0800 |
| Lucky13 | TLS 1.0–1.2 (CBC) | Timing side-channel on CBC padding validation | CVE-2013-0169 |
| CRIME | TLS 1.0–1.2 (with compression) | Recover session cookies via compression ratio oracle | CVE-2012-4929 |
| Downgrade to TLS 1.0 | All (if TLS 1.0 enabled) | MITM strips TLS version in ClientHello to force weaker protocol | N/A |
Protocol downgrade attack scenario
1# Normal connection (TLS 1.3):
2Client → Server: ClientHello (TLS 1.3, strong ciphers)
3Server → Client: ServerHello (TLS 1.3, AES-256-GCM)
4# ✅ Secure connection established
5
6# MITM downgrade attack (if server supports TLS 1.0):
7Client → MITM → Server: ClientHello (TLS 1.3, strong ciphers)
8MITM modifies: ClientHello (TLS 1.0, weak ciphers)
9Server → MITM → Client: ServerHello (TLS 1.0, RC4-SHA)
10# ❌ Weak connection — MITM can decrypt traffic!
11
12# Prevention: TLS_FALLBACK_SCSV (RFC 7507) detects downgrades
13# But the real fix: disable TLS 1.0 and 1.1 entirelyYour server supports TLS 1.0, 1.1, 1.2, and 1.3. Modern browsers use TLS 1.3. A colleague argues: 'TLS 1.0 is only there for legacy clients — modern browsers are fine.' What's the risk?
4. Weak Cipher Suites
Even with TLS 1.2, the security depends entirely on which cipher suites are enabled. A cipher suite specifies the key exchange algorithm, the bulk encryption algorithm, and the message authentication code (MAC).
Cipher Suite Security Classification
| Classification | Examples | Issue |
|---|---|---|
| ❌ NULL ciphers | TLS_RSA_WITH_NULL_SHA, TLS_NULL_WITH_NULL_NULL | No encryption at all — plaintext traffic |
| ❌ EXPORT ciphers | TLS_RSA_EXPORT_WITH_RC4_40_MD5 | 40/56-bit keys — trivially brute-forced (FREAK/Logjam attacks) |
| ❌ DES / 3DES | TLS_RSA_WITH_3DES_EDE_CBC_SHA | 64-bit block size vulnerable to Sweet32 birthday attack |
| ❌ RC4 | TLS_RSA_WITH_RC4_128_SHA | Statistical biases enable plaintext recovery (RFC 7465 prohibits RC4) |
| ❌ Static RSA | TLS_RSA_WITH_AES_256_CBC_SHA256 | No forward secrecy — compromised key decrypts all past traffic |
| ❌ CBC mode (TLS 1.0) | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA | Vulnerable to BEAST/Lucky13 in TLS 1.0, padding oracle attacks |
| ⚠️ CBC mode (TLS 1.2) | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 | Generally safe in TLS 1.2 but AEAD preferred |
| ✅ AEAD ciphers | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 | Authenticated encryption — no padding oracle, forward secrecy with ECDHE |
| ✅ ChaCha20-Poly1305 | TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 | Fast on devices without AES hardware, strong security |
Recommended TLS 1.2 cipher suite order
1# ✅ Strong cipher suite list for TLS 1.2 (Mozilla "Modern" profile):
2TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
3TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
4TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
5TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
6TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
7TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
8
9# Key properties of ALL recommended suites:
10# - ECDHE: Ephemeral key exchange → forward secrecy ✓
11# - GCM or POLY1305: AEAD encryption → no padding oracle ✓
12# - AES-128/256 or ChaCha20: Strong symmetric encryption ✓
13
14# ❌ Cipher suites to NEVER enable:
15# - Anything with NULL, EXPORT, DES, 3DES, RC4
16# - Anything with RSA key exchange (not ECDHE_RSA — static RSA)
17# - Anything with MD5 MACYou see this cipher suite in a server config: TLS_RSA_WITH_AES_256_GCM_SHA384. It uses AES-256 and GCM mode. Is it secure?
5. Certificate Misconfigurations
TLS certificates provide authentication — they prove the server is who it claims to be. Certificate misconfigurations undermine this trust, enabling man-in-the-middle attacks.
Common Certificate Issues
| Issue | What Happens | Attack Scenario |
|---|---|---|
| Expired certificate | Browsers show warning, users may click through | Users trained to ignore warnings → click through attacker's cert too |
| Self-signed certificate | Not trusted by any CA — browser warning | If users accept self-signed certs, they'll accept attacker's self-signed cert |
| Wrong hostname (CN/SAN mismatch) | Certificate is for a different domain | Indicates misconfigured server or potential impersonation |
| Incomplete certificate chain | Missing intermediate CA certificates | Some clients fail validation → fall back to HTTP or skip verification |
| Wildcard certificate overuse | *.example.com on all servers | Compromise of one server exposes private key for all subdomains |
| Weak signature algorithm (SHA-1) | Certificate signed with SHA-1 | Collision attacks can forge certificates (deprecated since 2017) |
| Long-lived certificates | Certificates valid for years | More time for key compromise; misses CA revocation cycles |
Dangerous: Disabling certificate verification in code
1// ❌ CRITICAL: Disabling TLS certificate verification
2// This is the most dangerous TLS mistake in application code
3
4// Node.js — disabling verification globally
5process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
6
7// Node.js — per-request
8const https = require('https');
9const agent = new https.Agent({ rejectUnauthorized: false });
10fetch('https://api.example.com', { agent });
11
12// Python requests
13import requests
14requests.get('https://api.example.com', verify=False)
15
16// Java
17TrustManager[] trustAll = new TrustManager[] {
18 new X509TrustManager() {
19 public X509Certificate[] getAcceptedIssuers() { return null; }
20 public void checkClientTrusted(X509Certificate[] c, String t) {}
21 public void checkServerTrusted(X509Certificate[] c, String t) {}
22 }
23};
24
25// Go
26http.Client{
27 Transport: &http.Transport{
28 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
29 },
30}
31
32// All of the above: COMPLETELY DISABLES CERTIFICATE VERIFICATION
33// A MITM attacker can present ANY certificate and it will be accepted!The "Development Mode" Excuse
Disabling certificate verification "for development" is the #1 source of TLS vulnerabilities in production code. Developers add rejectUnauthorized: false or verify=False to bypass self-signed certs in dev/staging, then forget to remove it before deployment. Use proper certificates in all environments — tools like mkcert generate locally-trusted certs for development without disabling verification.
6. HSTS & HTTP Downgrade Attacks
HTTP Strict Transport Security (HSTS) tells browsers to only connect to the site over HTTPS — never HTTP. Without HSTS, the first connection to a site (or any connection after the HSTS max-age expires) is vulnerable to an SSL stripping attack.
SSL stripping attack (without HSTS)
1# User types "example.com" in browser (no https:// prefix):
2# 1. Browser sends HTTP request to http://example.com
3# 2. Server redirects: HTTP 301 → https://example.com
4# 3. Browser follows redirect to HTTPS
5
6# SSL Stripping MITM attack (Moxie Marlinspike, 2009):
7# 1. User sends HTTP request to http://example.com
8# 2. MITM intercepts and connects to https://example.com on user's behalf
9# 3. MITM returns HTTP response to user (not HTTPS!)
10# 4. User sees http://example.com — no padlock, but many users don't notice
11# 5. ALL traffic flows through MITM in plaintext — credentials stolen!
12
13# The user thinks they're on the real site.
14# The server thinks it's talking to the user over HTTPS.
15# The MITM reads everything.HSTS header configuration
1# ✅ HSTS tells the browser: "NEVER connect over HTTP"
2Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
3
4# max-age=31536000 → Remember for 1 year (in seconds)
5# includeSubDomains → Apply to all subdomains too
6# preload → Eligible for browser's built-in HSTS preload list
7
8# After receiving this header over HTTPS, the browser will:
9# - Automatically convert ALL http:// requests to https://
10# - Refuse to connect if the certificate is invalid (no "click through")
11# - Remember this policy for max-age seconds
12
13# ❌ INSECURE: Common mistakes
14Strict-Transport-Security: max-age=0 # Disables HSTS!
15Strict-Transport-Security: max-age=3600 # Only 1 hour — too short
16# Missing entirely # No HSTS protection at allHSTS Preload List
Even with HSTS, the very first connection to a site is still vulnerable (the browser hasn't seen the HSTS header yet). The HSTS preload list solves this — it's a list of domains hardcoded into browsers that should always use HTTPS. Submit your domain at hstspreload.org. Requirements: serve a valid HTTPS certificate, redirect HTTP to HTTPS, serve the HSTS header with max-age ≥ 31536000, includeSubDomains, and preload.
Your server redirects HTTP to HTTPS (301 redirect) but does not set the HSTS header. A colleague says the redirect is sufficient. Is this correct?
7. Detection During Code Review
TLS misconfigurations appear in multiple places: server configuration files, application code making HTTPS requests, and missing security headers. Here is a systematic detection approach.
Code Review Detection Patterns
| Pattern | What to Look For | Risk |
|---|---|---|
| rejectUnauthorized: false | Node.js HTTPS requests with disabled cert verification | Critical |
| verify=False | Python requests library with disabled cert verification | Critical |
| InsecureSkipVerify: true | Go HTTP client with disabled cert verification | Critical |
| NODE_TLS_REJECT_UNAUTHORIZED=0 | Global TLS bypass via environment variable | Critical |
| SSLv3 / TLSv1.0 / TLSv1.1 | Server configs enabling deprecated protocols | High |
| RC4 / DES / 3DES / NULL / EXPORT | Weak cipher suites in server config | Critical |
| Missing HSTS header | No Strict-Transport-Security in response headers | High |
| Mixed content (http:// in HTTPS page) | HTTP resources loaded on HTTPS pages | High |
| Self-signed certs in production | Custom CA trust stores or cert pinning workarounds | Critical |
| Hardcoded certificates/keys | Private keys or certificates in source code | Critical |
Quick grep patterns for TLS issues
1# Disabled certificate verification
2grep -rn "rejectUnauthorized.*false|REJECT_UNAUTHORIZED.*0" --include="*.js" --include="*.ts" --include="*.env"
3
4grep -rn "verify.*=.*False|verify.*False" --include="*.py"
5
6grep -rn "InsecureSkipVerify.*true" --include="*.go"
7
8# Weak protocols in config
9grep -rn "SSLv2|SSLv3|TLSv1.0|TLSv1.1|ssl_protocols.*TLSv1[^.]" --include="*.conf" --include="*.yaml" --include="*.yml"
10
11# Weak ciphers
12grep -rn "RC4|DES|NULL|EXPORT|3DES|aNULL|eNULL" --include="*.conf" --include="*.yaml" --include="*.yml"
13
14# Missing HSTS (check server framework middleware)
15grep -rn "strict-transport|hsts|Strict-Transport" --include="*.js" --include="*.ts" --include="*.conf"
16
17# Mixed content
18grep -rn "http://" --include="*.html" --include="*.tsx" --include="*.jsx" | grep -v "localhost|127.0.0.1|http://schemas"
19
20# External scan: SSL Labs server test
21# https://www.ssllabs.com/ssltest/You find this in an environment file: NODE_TLS_REJECT_UNAUTHORIZED=0. The developer says it's only for the staging environment. Should you flag this?