HTTP Security Headers Testing Guide

HTTP security headers are a browser-enforced layer of defence that instructs clients how to handle your application's content. When they are absent, weak, or misconfigured they expose users to clickjacking, cross-site scripting, protocol downgrade attacks, and cross-origin data leakage — often without any vulnerability in the application code itself. A single missing header can turn a hardened app into an easy target.

This guide walks through each major security header, what it does, how attackers exploit its absence or misconfiguration, and how to test for issues both manually and at scale. Coverage maps to OWASP Testing Guide v4.2 section OTG-CONFIG-007 and the broader Security Misconfiguration category (OWASP A05).

Quick Enumeration: Dump All Response Headers

Before diving into individual headers, get a full picture of what the server is actually returning. Use curl with verbose output, then filter for relevant headers:

# Dump all response headers for a target
curl -sI https://target.example.com | grep -iE \
  'content-security-policy|strict-transport|x-frame|x-content-type|referrer-policy|permissions-policy|x-xss-protection|x-powered-by|server:'

# Follow redirects and check final destination headers
curl -sIL https://target.example.com

# Check headers over HTTP to catch missing HSTS redirect
curl -sI http://target.example.com | grep -i 'strict-transport\|location'

# Fetch headers from a specific path (headers can vary per route)
curl -sI https://target.example.com/admin/dashboard

For automated baseline scoring, securityheaders.com gives a letter grade and flags every missing or misconfigured header. It is a useful first pass but never a substitute for manual review — it cannot evaluate whether a CSP policy is actually restrictive or merely present.

Content Security Policy (CSP)

CSP is the most powerful and most commonly misconfigured security header. It tells the browser which origins are permitted to load scripts, styles, images, frames, and other resources. A correct policy eliminates the browser's ability to execute injected scripts even when an XSS payload is reflected or stored.

Testing for weak or absent CSP

A missing Content-Security-Policy header means the browser imposes no restrictions on resource loading. But a present policy can be equally dangerous if it contains bypass-enabling directives:

  • unsafe-inline in script-src — nullifies XSS protection entirely; inline <script> tags execute freely
  • unsafe-eval — allows eval(), setTimeout(string), and Function() constructor, all common XSS sinks
  • Wildcard * in script-src — any origin can serve scripts, defeating the policy
  • JSONP endpoints as allowed origins — an attacker can use a trusted JSONP endpoint to inject arbitrary script callbacks
  • Missing default-src — unspecified resource types fall back to browser default (allow-all)
  • object-src unset or set to * — Flash/plugin-based XSS remains possible

Parse the policy programmatically to catch these patterns:

# Extract and pretty-print CSP directives
curl -sI https://target.example.com \
  | grep -i 'content-security-policy' \
  | sed 's/.*: //' \
  | tr ';' '\n' \
  | sed 's/^ //' \
  | sort

# Check for the most dangerous bypasses
curl -sI https://target.example.com \
  | grep -i 'content-security-policy' \
  | grep -iE "unsafe-inline|unsafe-eval|\*|data:|blob:"

# Report-Only mode means the policy is not enforced — flag it
curl -sI https://target.example.com \
  | grep -i 'content-security-policy-report-only'

Use Google's CSP Evaluator (csp-evaluator.withgoogle.com) for automated bypass detection against a known list of JSONP and Angular CDN endpoints that allow policy circumvention.

HTTP Strict Transport Security (HSTS)

HSTS instructs browsers to connect only over HTTPS for a specified duration, preventing SSL stripping attacks where an on-path attacker downgrades the connection to plaintext HTTP before the browser is redirected. Without HSTS, tools like sslstrip can silently intercept credentials on any network the victim passes through.

A valid HSTS header looks like:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Key directives to verify during testing:

Directive Recommended Value Risk if Missing/Weak
max-age ≥ 31536000 (1 year) Short values expire quickly; browsers revert to HTTP after expiry, re-enabling stripping attacks
includeSubDomains Present Subdomains remain vulnerable to stripping even when the apex domain is protected
preload Present (with HSTS preload submission) First visit before the header is cached still vulnerable; preload eliminates this bootstrap window
Header on HTTP response Should NOT appear HSTS over HTTP is ignored by browsers per RFC 6797; misconfiguration that may indicate server confusion

Also check that the site is submitted to the HSTS preload list at hstspreload.org. Being on the preload list eliminates the trust-on-first-use (TOFU) vulnerability window for Chrome, Firefox, Safari, and Edge.

X-Frame-Options and Clickjacking

X-Frame-Options controls whether a page can be embedded in a <frame>, <iframe>, or <object>. Without it, an attacker can overlay a transparent iframe of the target application over a decoy page, tricking authenticated users into unknowingly clicking buttons — completing purchases, changing account settings, or authorising OAuth flows.

Valid values are DENY (no framing at all) and SAMEORIGIN (framing only from the same origin). The deprecated ALLOW-FROM uri value is not supported by Chrome or Firefox and should be treated as absent.

Note that X-Frame-Options is superseded by the frame-ancestors CSP directive, which offers per-origin granularity. If both are present, CSP takes precedence in modern browsers. Check for conflicting signals:

  • X-Frame-Options: DENY present but CSP includes frame-ancestors * — CSP wins, framing is allowed
  • Neither header present — clickjacking is trivially exploitable on any page with authenticated actions
  • X-Frame-Options set on the login page but absent on sensitive internal pages (common partial deployment)

Test by embedding the target in a local HTML file and loading it in your browser:

<!-- Save as /tmp/clickjack_test.html and open in browser -->
<!DOCTYPE html>
<html>
<body style="background:#f00;">
  <iframe src="https://target.example.com/account/settings"
          width="800" height="600"
          style="opacity:0.5; position:absolute; top:0; left:0;">
  </iframe>
  <p>Click the red button to win a prize!</p>
</body>
</html>

If the page loads inside the iframe, clickjacking protection is absent. A correctly configured application will render a blank frame with a console error such as "Refused to display in a frame because it set 'X-Frame-Options' to 'deny'".

X-Content-Type-Options and MIME Sniffing

X-Content-Type-Options: nosniff disables browser MIME-type sniffing, preventing Internet Explorer and legacy Edge from executing a response as a different content type than the server declared. Without it, a file upload endpoint that stores user content can be abused: upload an HTML file with a .jpg extension, and older browsers may sniff the content and render it as HTML, executing any embedded JavaScript.

The fix is a single-value header — there is no configuration beyond presence or absence:

  • Present: X-Content-Type-Options: nosniff — correct
  • Absent: MIME sniffing enabled, file upload XSS and cross-origin script execution are possible
  • Wrong value: any value other than nosniff is ignored; treat as absent

While modern Chrome and Firefox perform limited sniffing regardless of this header, it remains mandatory for defence-in-depth and is a trivial finding on any scan.

Referrer-Policy

Referrer-Policy controls how much information the browser includes in the Referer request header when navigating to external sites or loading sub-resources. Without a restrictive policy, the full URL of every page — including query parameters containing tokens, search terms, and user identifiers — is sent to third-party analytics, CDN providers, and any site linked from your application.

The most common real-world impact: a password reset link containing a secret token in the URL is sent as a Referer header to a third-party analytics script embedded on the reset confirmation page. The token is now in the analytics provider's logs and potentially accessible to other customers sharing the same analytics dashboard.

Recommended policy values from most to least restrictive:

Policy Value What Is Sent Use Case
no-referrer Nothing Maximum privacy; breaks some analytics
strict-origin Origin only (no path) on same-protocol requests Good default for most applications
strict-origin-when-cross-origin Full URL for same-origin; origin only cross-origin Browser default since 2021; reasonable balance
unsafe-url Full URL always Dangerous — leaks tokens and PII to third parties
Absent Browser default (strict-origin-when-cross-origin) Acceptable but explicit policy is better practice

During testing, inspect any page that handles sensitive data in the URL (OAuth callbacks, password reset links, session tokens in GET parameters) and verify the policy prevents full-URL leakage to cross-origin resources loaded on those pages.

Permissions-Policy (formerly Feature-Policy)

Permissions-Policy controls browser feature access — camera, microphone, geolocation, accelerometer, payment, and dozens of others — at the HTTP response level. It applies both to the page itself and to any iframes embedded within it. This is critical for applications that embed third-party widgets: a malicious or compromised ad network script cannot silently activate the user's microphone if the policy denies it.

A restrictive baseline policy denies all powerful features unless explicitly required:

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(),
  usb=(), accelerometer=(), gyroscope=(), magnetometer=(),
  fullscreen=(self), autoplay=(self)

Testing approach: check the header is present and evaluate whether any sensitive features are granted to third-party origins ((self "https://cdn.example.com") syntax). Feature access granted to * is equivalent to no restriction. Also verify the policy applies to any <iframe> elements — a page-level policy does not restrict same-origin iframes unless the allow attribute on the iframe element propagates it correctly.

Headers That Should Not Be Present

Security testing is not only about what headers are missing — it is also about headers that should be removed. Information-disclosure headers give attackers a map of the technology stack before a single exploit is attempted.

  • Server — reveals the web server software and version (e.g., Apache/2.4.51 (Ubuntu)). Directly enables version-targeted exploit searches.
  • X-Powered-By — exposes the application framework and version (e.g., PHP/8.1.12, ASP.NET). Should be suppressed in all production configurations.
  • X-AspNet-Version / X-AspNetMvc-Version — .NET-specific version disclosure headers, enabled by default in older ASP.NET configurations.
  • X-XSS-Protection — the legacy IE/Chrome XSS auditor is deprecated and removed. Setting X-XSS-Protection: 1; mode=block can introduce new vulnerabilities in some browser versions. The correct value is 0 or omit the header entirely; rely on CSP instead.

Automated Header Auditing at Scale

Manual curl checks are sufficient for a single target, but production environments often have hundreds of routes, multiple subdomains, and CDN layers that introduce their own header modifications. Use nikto for broad web server misconfiguration scanning, or write a targeted Nuclei template to audit headers across an entire asset inventory:

# Scan with Nikto for missing security headers
nikto -h https://target.example.com -Tuning x -output nikto-headers.txt

# Use Nuclei with the security-headers template pack
nuclei -u https://target.example.com \
  -t ~/nuclei-templates/misconfiguration/http-missing-security-headers.yaml \
  -severity low,medium,high

# Batch audit across a list of subdomains
cat subdomains.txt | nuclei \
  -t ~/nuclei-templates/misconfiguration/ \
  -tags headers \
  -rate-limit 50 \
  -o headers-findings.json \
  -json

# Extract just the missing headers from JSON output
cat headers-findings.json \
  | jq -r '[.host, .info.name, .info.severity] | @tsv' \
  | sort -k3

Remember that CDN providers (Cloudflare, Fastly, CloudFront) often add or strip headers at the edge. Always test against the raw origin server as well as the CDN-fronted hostname to detect discrepancies. A header present on the CDN edge but absent from the origin means a cache bypass or direct origin access attack removes the protection entirely.


HTTP security headers are low-effort, high-impact controls. Each one described here can be configured in an afternoon, requires no application code changes, and blocks entire classes of client-side attack. The most dangerous finding is not a missing header in isolation — it is the combination of absent Content-Security-Policy, absent X-Frame-Options, and an application that reflects user input. Together, they create a trivially exploitable XSS-and-clickjacking chain with no application vulnerability required.

Ironimo automatically audits every HTTP security header on every response across your web application — including headers that vary by route, authenticated state, or subdomain. Each scan produces a prioritised finding list with remediation guidance specific to your server stack.

Stop running manual curl checks. Get continuous header coverage alongside full OWASP Top 10 scanning, all powered by the same Kali Linux toolchain professional pentesters use.

Start free scan
← Back to blog