← Back to All Vulnerabilities

Content Security Policy (CSP) Bypass Vulnerabilities

Content Security Policy is a security standard that helps prevent cross-site scripting (XSS) and other code injection attacks. However, CSP can be bypassed through various techniques if it's not properly configured.

Understanding Content Security Policy

1. What is CSP?

Content Security Policy is an HTTP header that allows website administrators to control resources the user agent is allowed to load for a given page. It helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks.

2. How CSP Works

CSP works by specifying which domains the browser should consider to be valid sources of executable scripts. A CSP compatible browser will then only execute scripts loaded from these whitelisted domains, ignoring all other scripts (including inline scripts and event-handling HTML attributes).

3. CSP Directives

Directive Description Example
default-src Fallback for other resource types default-src 'self'
script-src Valid sources for JavaScript script-src 'self' trusted.com
style-src Valid sources for stylesheets style-src 'self' 'unsafe-inline'
img-src Valid sources for images img-src 'self' img.com
connect-src Valid targets for fetch, XHR, WebSocket connect-src 'self' api.example.com
font-src Valid sources for fonts font-src 'self' fonts.gstatic.com
object-src Valid sources for plugins object-src 'none'
media-src Valid sources for audio and video media-src media.example.com
frame-src Valid sources for frames frame-src 'self'
report-uri Where to send CSP violation reports report-uri /csp-report

4. CSP Source Values

CSP Bypass Demonstrations

Vulnerable CSP Configurations

This demo shows how different CSP configurations can be vulnerable to bypasses.

Content-Security-Policy: default-src 'self'; script-src 'self'
' placeholder="Enter potentially malicious input">

Demo Result:

Click "Test CSP" to see if the selected CSP configuration is vulnerable.

Why This Is Vulnerable:

Missing critical directives means the browser will use default values for those not specified, potentially allowing unsafe behaviors.

Common CSP Bypass Techniques

This section illustrates different methods attackers use to bypass Content Security Policy restrictions.

Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com

Demo Result:

Click "Test Bypass" to see if the selected technique can bypass the CSP.

How This Bypass Works:

AngularJS template injection can bypass CSP restrictions if AngularJS is allowed from a CDN. This works because AngularJS evaluates expressions within curly braces, which doesn't count as JavaScript execution from CSP's perspective.

Secure CSP Implementation

This section demonstrates how to create a robust Content Security Policy that mitigates bypass techniques.

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'
' placeholder="Enter potentially malicious input">

Demo Result:

Click "Test CSP" to see how the secure CSP configuration protects against attacks.

Security Benefits:

This basic CSP configuration provides protection against XSS by restricting script execution to the same origin. It also prevents the use of dangerous features like object embeds.

Best Practices for CSP Implementation

1. Create a Strong Base Policy

Content-Security-Policy: 
  default-src 'none';
  script-src 'self';
  connect-src 'self';
  img-src 'self';
  style-src 'self';
  font-src 'self';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  object-src 'none'
        

This policy uses a "deny by default" approach, only allowing resources from your own domain. It explicitly blocks potentially dangerous resources like objects and frames from other origins.

2. Avoid Unsafe Directives

3. Use Nonces or Hashes Instead of 'unsafe-inline'

// Server-side code generates a random nonce for each response
const nonce = crypto.randomBytes(16).toString('base64');

// Add the nonce to your CSP header
response.setHeader('Content-Security-Policy', `script-src 'self' 'nonce-${nonce}'`);

// In your HTML, add the nonce to each script tag
<script nonce="<%= nonce %>">
  // This inline script will be allowed by CSP
  console.log('Hello, secure world!');
</script>
        

4. Use Subresource Integrity (SRI) with External Resources

<script 
  src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" 
  integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK" 
  crossorigin="anonymous">
</script>
        

This ensures that external scripts haven't been tampered with. Add this to your CSP:

Content-Security-Policy: ...; require-sri-for script style
        

5. Implement CSP in Report-Only Mode First

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-violation-report
        

This allows you to collect violation reports without blocking any resources, helping you refine your policy before full enforcement.

6. Use CSP Level 3 Features for Enhanced Protection

Content-Security-Policy: script-src 'nonce-{random}' 'strict-dynamic'; object-src 'none'; base-uri 'none'
        

7. Regularly Audit and Update Your CSP

= document.getElementById('vulnerableInput').value; const resultDiv = document.getElementById('cspDemoResult'); // Get the CSP based on the selected option let cspHeader; switch(option) { case 'missing-directives': cspHeader = "default-src 'self'"; break; case 'unsafe-inline': cspHeader = "default-src 'self'; script-src 'self' 'unsafe-inline'"; break; case 'unsafe-eval': cspHeader = "default-src 'self'; script-src 'self' 'unsafe-eval'"; break; case 'wildcard-domains': cspHeader = "default-src 'self'; script-src 'self' https://*"; break; case 'jsonp': cspHeader = "default-src 'self'; script-src 'self' https://ajax.googleapis.com"; break; case 'data-uri': cspHeader = "default-src 'self'; script-src 'self' data:"; break; } // Clear previous results resultDiv.innerHTML = ''; // Create a sandboxed iframe with the user's input and the selected CSP const frameContainer = createSandboxedFrame(userInput, cspHeader); // Add explanation of the result const explanation = document.createElement('p'); if (option === 'unsafe-inline') { explanation.innerHTML = `

With 'data:' URIs allowed in script-src, attackers can inject base64-encoded JavaScript.

For example: <script src="data:text/javascript;base64,YWxlcnQoJ1hTUyBBdHRhY2shJyk="></script> would execute.

`; } else if (option === 'missing-directives') { explanation.innerHTML = `

Missing critical directives means the browser falls back to default behaviors, which may be insecure.

For example, without object-src defined, Flash objects could be embedded and exploited.

`; } else if (option === 'wildcard-domains') { explanation.innerHTML = `

With wildcard domains allowed, attackers can host malicious JavaScript on any HTTPS site.

This is equivalent to having no CSP for script sources.

`; } else if (option === 'jsonp') { explanation.innerHTML = `

JSONP endpoints can be abused to execute arbitrary JavaScript if included in your whitelist.

For example: <script src="https://ajax.googleapis.com/ajax/services/feed/find?v=1.0&q=xss&callback=alert"></script>

`; } resultDiv.appendChild(frameContainer); resultDiv.appendChild(explanation); } // Bypass techniques demo function updateBypassTechniqueDemo() { const technique = document.getElementById('bypassTechnique').value; const policyDisplay = document.getElementById('bypassCspDisplay'); const payloadField = document.getElementById('bypassPayload'); const explanationDiv = document.getElementById('bypassExplanation'); switch(technique) { case 'angular': policyDisplay.textContent = "Content-Security-Policy: script-src 'self' https://ajax.googleapis.com/ajax/libs/angularjs/"; payloadField.value = "
{{constructor.constructor('alert(\"Angular template injection bypass\")')()}}
"; explanationDiv.innerHTML = `

AngularJS template injection can bypass CSP restrictions because AngularJS evaluates expressions within curly braces, which doesn't count as JavaScript execution from CSP's perspective. If AngularJS is loaded from a whitelisted domain, this can be exploited.

`; break; case 'jsonp': policyDisplay.textContent = "Content-Security-Policy: script-src 'self' https://ajax.googleapis.com"; payloadField.value = ""; explanationDiv.innerHTML = `

JSONP endpoints can be abused if they're on domains in your whitelist. These endpoints wrap JSON data in a callback function, and if this callback parameter isn't properly validated, it can be used to execute arbitrary JavaScript.

`; break; case 'dom-based': policyDisplay.textContent = "Content-Security-Policy: script-src 'self' 'unsafe-eval'"; payloadField.value = "location.href = 'javascript:' + decodeURIComponent('alert%28%22DOM-based%20XSS%20bypass%22%29')"; explanationDiv.innerHTML = `

DOM-based XSS can bypass CSP if the page uses dangerous functions like eval() or document.write() with user input. This example exploits a page that allows 'unsafe-eval' by manipulating the DOM through allowed JavaScript.

`; break; case 'iframe': policyDisplay.textContent = "Content-Security-Policy: default-src 'self'; frame-src *"; payloadField.value = ""; explanationDiv.innerHTML = `

If your CSP allows iframes (frame-src) but doesn't properly restrict them, attackers can use the srcdoc attribute to inject HTML and JavaScript. The content of srcdoc is governed by the parent page's origin, not by CSP's frame-src directive.

`; break; case 'script-gadgets': policyDisplay.textContent = "Content-Security-Policy: script-src 'self'; object-src 'none'"; payloadField.value = "
alert(\"Script gadget bypass\")'\">
"; explanationDiv.innerHTML = `

Script gadgets are legitimate JavaScript functions that can be repurposed for attacks. This example uses Knockout.js's data binding to execute HTML that contains a script tag. Even though the CSP blocks inline scripts, the framework processes the HTML and executes the code.

`; break; case 'object-src': policyDisplay.textContent = "Content-Security-Policy: script-src 'self'; object-src 'self'"; payloadField.value = ""; explanationDiv.innerHTML = `

If object-src isn't set to 'none', attackers can use <object> tags with data URIs to execute JavaScript. The example uses a base64-encoded HTML document with a script that would execute in browsers that support this bypass.

`; break; } } function testBypassTechnique() { const technique = document.getElementById('bypassTechnique').value; const payload = document.getElementById('bypassPayload').value; const resultDiv = document.getElementById('bypassDemoResult'); // Get the CSP based on the selected technique let cspHeader; switch(technique) { case 'angular': cspHeader = "script-src 'self' https://ajax.googleapis.com/ajax/libs/angularjs/"; break; case 'jsonp': cspHeader = "script-src 'self' https://ajax.googleapis.com"; break; case 'dom-based': cspHeader = "script-src 'self' 'unsafe-eval'"; break; case 'iframe': cspHeader = "default-src 'self'; frame-src *"; break; case 'script-gadgets': cspHeader = "script-src 'self'; object-src 'none'"; break; case 'object-src': cspHeader = "script-src 'self'; object-src 'self'"; break; } // Clear previous results resultDiv.innerHTML = ''; // Add explanation based on the technique let explanation; switch(technique) { case 'angular': explanation = `

In a real environment with AngularJS loaded, this payload would execute JavaScript through Angular's template engine. The CSP wouldn't block it because the code execution happens through Angular's legitimate JavaScript rather than through a script tag or event handler.

`; break; case 'jsonp': explanation = `

In a real environment, this would load a JSONP endpoint with 'alert' as the callback function. When the response arrives, it would execute alert('data') instead of the expected callback function.

`; break; case 'dom-based': explanation = `

This attack uses a javascript: URL with encoded content to bypass CSP. If the page uses this value in an unsafe way (like in eval() or innerHTML), the payload would execute despite CSP restrictions.

`; break; case 'iframe': explanation = `

The srcdoc attribute creates a new document within the iframe that inherits the parent page's origin. This means scripts within it execute in the context of your domain, bypassing CSP frame-src restrictions.

`; break; case 'script-gadgets': explanation = `

If the page uses a framework like Knockout.js, this payload would cause the framework to process the HTML string and execute the script. The CSP doesn't block this because the script execution happens through the legitimate framework code.

`; break; case 'object-src': explanation = `

This creates an object element with a data URI containing HTML and JavaScript. If object-src isn't restricted to 'none', this could execute in supported browsers, bypassing script-src restrictions.

`; break; } // Create a demonstration container const demoDiv = document.createElement('div'); demoDiv.innerHTML = `

CSP Bypass Demonstration

Payload: ${payload}

${explanation}

Note: For security reasons, this demo doesn't actually execute the bypass, but shows how it would work in a vulnerable environment.

`; resultDiv.appendChild(demoDiv); } // Secure CSP demo function updateSecureCSP() { const level = document.getElementById('securityLevel').value; const policyDisplay = document.getElementById('secureCSPDisplay'); const explanationDiv = document.getElementById('securityExplanation'); switch(level) { case 'basic': policyDisplay.textContent = "Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'"; explanationDiv.innerHTML = `

This basic CSP configuration provides good protection against common attacks:

`; break; case 'strict': policyDisplay.textContent = "Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; font-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'none'; object-src 'none'; require-trusted-types-for 'script'"; explanationDiv.innerHTML = `

This strict policy uses a "deny by default" approach, explicitly defining all allowed resource types:

`; break; case 'nonce': const nonce = 'random123'; // In a real scenario, this would be generated per request policyDisplay.textContent = `Content-Security-Policy: default-src 'none'; script-src 'self' 'nonce-${nonce}'; connect-src 'self'; img-src 'self'; style-src 'self'; font-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'none'; object-src 'none'`; explanationDiv.innerHTML = `

This nonce-based policy allows specific inline scripts without 'unsafe-inline':

`; break; case 'hash': policyDisplay.textContent = "Content-Security-Policy: default-src 'none'; script-src 'self' 'sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng='; connect-src 'self'; img-src 'self'; style-src 'self'; font-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'none'; object-src 'none'"; explanationDiv.innerHTML = `

This hash-based policy allows specific inline scripts by their hash:

`; break; case 'report-only': policyDisplay.textContent = "Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; object-src 'none'; report-uri /csp-violation-report"; explanationDiv.innerHTML = `

Report-Only mode lets you test a CSP without enforcing it:

`; break; } } function testSecureCSP() { const level = document.getElementById('securityLevel').value; const userInput `

With 'unsafe-inline' allowed, XSS attacks through inline scripts can succeed.

Check the iframe above to see if the script executed. In a real browser with this CSP, an alert would appear.

`; } else if (option === 'unsafe-eval') { explanation.innerHTML = `

With 'unsafe-eval' allowed, attackers can use eval() and similar functions if they control input to those functions.

This is particularly dangerous in applications that parse and execute JSON or other user-controlled data.

`; } else if (option === 'data-uri') { explanation.innerHTML =