## 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.*