Skip to content

Latest commit

 

History

History
232 lines (175 loc) · 10.5 KB

File metadata and controls

232 lines (175 loc) · 10.5 KB

Lab HTTP Header Auditor

Fetch a target URL and audit its HTTP response headers for missing or misconfigured security headers.

Warning

Send requests only to sites you own or are authorized to test. This tool makes a single benign GET/HEAD request per URL — no fuzzing, no auth bypass — but any request to a third party is still traffic against their infrastructure. Use a local app or your own site for the lab. Only run these labs against systems you own or are explicitly authorized to test.

Objective

  • Issue an HTTP request and read response headers with requests.
  • Check for the presence of key security headers.
  • Detect information-leaking headers (Server, X-Powered-By).
  • Produce a pass/fail audit report with remediation hints.

Learning Outcomes

After completing this lab you will be able to:

  • Issue HTTP requests and inspect response headers programmatically.
  • Explain what each major security header does and what its absence permits.
  • Grade a site's header posture and produce actionable remediation advice.
  • Handle redirects, timeouts, and TLS errors without crashing.
  • Describe why headers are one signal among many, not a security verdict.

Prerequisites

  • [[requests]] — HTTP sessions and response objects (or [[Urllib-Module|urllib]] for a stdlib-only version).
  • [[Dictionaries]] — headers are a case-insensitive mapping.
  • [[Error-and-Exception-Handling/Readme|Error & Exception Handling]] — network failures are routine.
  • [[SSL-Module|SSL Module]] — understanding TLS errors you will encounter.

Lab Environment

Item Detail
Python 3.8+
Package requests (pip install requests)
Target A local test app (started in Setup) or your own site

Setup

mkdir -p ~/labs/header-audit && cd ~/labs/header-audit
python3 -m venv .venv
source .venv/bin/activate
pip install requests

# Start a local app that deliberately omits security headers:
python3 -m http.server 8080 &     # serves the current dir on :8080

nano audit.py    # paste the Code section

Tasks

  1. Fetch a response. Request a URL with a timeout and print the status code and final URL after redirects.
  2. List the headers. Print every response header; note that the mapping is case-insensitive.
  3. Define the checklist. Build a dictionary of the headers you care about — Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy.
  4. Check presence. Report each as present or missing, with a one-line explanation of the risk when absent.
  5. Check values, not just presence. A Content-Security-Policy containing unsafe-inline is weaker than one without; HSTS with a very short max-age provides little protection.
  6. Flag information disclosure. Report Server and X-Powered-By values that leak product versions.
  7. Grade and report. Produce a simple score with concrete remediation for each gap.

Complete Example Code

#!/usr/bin/env python3
"""Audit a URL's HTTP response for security-header hygiene."""
import argparse
import sys

import requests

# header -> why it matters (shown when missing)
EXPECTED = {
    "Strict-Transport-Security": "Forces HTTPS; prevents SSL-strip downgrade.",
    "Content-Security-Policy": "Mitigates XSS and data-injection.",
    "X-Content-Type-Options": "Stops MIME-sniffing (set to 'nosniff').",
    "X-Frame-Options": "Prevents clickjacking via framing.",
    "Referrer-Policy": "Controls Referer leakage to third parties.",
    "Permissions-Policy": "Restricts powerful browser features.",
}

# Headers that leak stack/version info if present.
LEAKY = ["Server", "X-Powered-By", "X-AspNet-Version", "X-Generator"]


def audit(url: str, timeout: float = 10.0) -> int:
    try:
        resp = requests.get(url, timeout=timeout, allow_redirects=True)
    except requests.RequestException as exc:
        print(f"[!] Request failed: {exc}")
        return 2

    print(f"[*] {resp.status_code} {resp.reason}  {resp.url}\n")
    headers = {k.lower(): v for k, v in resp.headers.items()}

    print("Security headers:")
    missing = 0
    for name, reason in EXPECTED.items():
        if name.lower() in headers:
            print(f"  [PASS] {name}: {headers[name.lower()]}")
        else:
            missing += 1
            print(f"  [FAIL] {name} — MISSING ({reason})")

    print("\nInformation disclosure:")
    leaks = 0
    for name in LEAKY:
        if name.lower() in headers:
            leaks += 1
            print(f"  [WARN] {name}: {headers[name.lower()]}  (consider removing)")
    if not leaks:
        print("  [OK] No obvious version-leaking headers.")

    print(f"\n[*] {missing} missing security header(s), {leaks} leaky header(s).")
    return 1 if (missing or leaks) else 0


def main() -> None:
    parser = argparse.ArgumentParser(description="HTTP security-header auditor.")
    parser.add_argument("url", help="Full URL, e.g. https://example.com")
    args = parser.parse_args()
    sys.exit(audit(args.url))


if __name__ == "__main__":
    main()

Expected Output

$ python audit.py http://127.0.0.1:8080
[*] 200 OK  http://127.0.0.1:8080/

Security headers:
  [FAIL] Strict-Transport-Security — MISSING (Forces HTTPS; prevents SSL-strip downgrade.)
  [FAIL] Content-Security-Policy — MISSING (Mitigates XSS and data-injection.)
  [FAIL] X-Content-Type-Options — MISSING (Stops MIME-sniffing (set to 'nosniff').)
  [FAIL] X-Frame-Options — MISSING (Prevents clickjacking via framing.)
  [FAIL] Referrer-Policy — MISSING (Controls Referer leakage to third parties.)
  [FAIL] Permissions-Policy — MISSING (Restricts powerful browser features.)

Information disclosure:
  [WARN] Server: SimpleHTTP/0.6 Python/3.11.2  (consider removing)

[*] 6 missing security header(s), 1 leaky header(s).

Explanation

  • requests.get with allow_redirects=True follows the redirect chain and reports the final URL's headers — important because HSTS and CSP are often only set on the HTTPS endpoint after an HTTP→HTTPS redirect.
  • Case-insensitive lookup (k.lower()) matters because HTTP header names are case-insensitive; a server may send content-security-policy or Content-Security-Policy.
  • Missing security headers are common real findings: no CSP means the site relies entirely on other XSS defences; no HSTS leaves the first request downgradeable.
  • Leaky headers like Server: Apache/2.4.29 hand an attacker a version to match against a CVE list. Suppressing them is defence-in-depth, not a fix.
  • The script returns a non-zero exit code on any finding, so it can gate a CI pipeline (python audit.py $URL || echo "harden headers").

Validation

python3 headeraudit.py http://127.0.0.1:8000
[*] http://127.0.0.1:8000  ->  200 OK

MISSING  Strict-Transport-Security   HTTPS not enforced on future visits
MISSING  Content-Security-Policy     No restriction on script sources (XSS risk)
MISSING  X-Content-Type-Options      MIME-type sniffing permitted
MISSING  X-Frame-Options             Page can be framed (clickjacking risk)
INFO     Server: SimpleHTTP/0.6 Python/3.13.7   version disclosure

Score: 0/6 security headers present
  • A bare http.server instance scores 0/6 — it sets no security headers.
  • Adding a header via a proxy or a custom handler changes the score.
  • An unreachable host produces a clear error, not a traceback.
  • A redirect is followed and the final URL is reported.
  • Header lookup is case-insensitive (content-security-policy matches).

Challenges

  1. Read a list of URLs from a file and audit each, printing a summary table.
  2. Score each site A–F based on how many headers pass (mirror securityheaders.com).
  3. Parse the CSP value and warn on weakeners like unsafe-inline or *.
  4. Add a --json flag emitting machine-readable results for a dashboard.
  5. Check cookies for the Secure, HttpOnly, and SameSite attributes and report insecure ones.

Troubleshooting

Symptom Likely cause Fix
ConnectionError Nothing listening on the target port Start the local server from the Setup step
SSLError: certificate verify failed Self-signed certificate in the lab Fix the trust store, or use verify= with the CA bundle — do not reach for verify=False
Script hangs No timeout on the request Pass timeout=10 to every request
Header reported missing but visible in the browser Header set on a redirect target, not the first response Inspect response.history as well as the final response
KeyError on a header Direct indexing instead of .get() Use response.headers.get(name)
ModuleNotFoundError: requests Package not installed in the active venv python3 -m pip install requests, or use the urllib variant

Security Notes

  • Audit only sites you own or are authorized to test. Requesting a page is low impact, but automated scanning across a domain is still unauthorized activity without permission.
  • Never disable TLS verification. verify=False makes the audit meaningless and trains a bad habit — a tool that ignores certificate errors cannot report on them.
  • Headers are one signal, not a verdict. A perfect header score says nothing about authentication, authorization, or injection flaws. Do not let a green report imply the application is secure.
  • Server and X-Powered-By disclosure is low severity on its own but is useful to an attacker for version-specific exploit selection. Report it as informational.
  • Respect rate limits and robots.txt if you extend this to crawl multiple pages.
  • Set a descriptive User-Agent identifying your tool and the engagement, so the target's operators can attribute the traffic.

Cleanup

kill %1 2>/dev/null      # stop the local http.server
deactivate
rm -rf ~/labs/header-audit

Further Reading

Related

  • [[API-Client]] — related Mini-Project
  • [[Subdomain-Enumerator]] — feed live hosts into this auditor
  • [[Readme|Python for Security Professionals]] — course home