Content Security Policy is the most effective browser-side defense against cross-site scripting. A properly configured CSP prevents injected scripts from executing, which neutralizes the most dangerous consequence of XSS vulnerabilities. But "properly configured" is doing a lot of heavy lifting in that sentence.
Research consistently shows that the majority of deployed CSPs are bypassable. Google's analysis of CSPs across the web found that 94.7% of policies that attempted to restrict script execution were ineffective due to configuration weaknesses. The problem is not CSP the technology. It is CSP the deployment.
Common Bypass Techniques
unsafe-inline
The most obvious bypass is the unsafe-inline directive in script-src. Many CSP deployments include it because the application uses inline scripts and converting them to external files is considered too much work:
Content-Security-Policy: script-src 'self' 'unsafe-inline'
With unsafe-inline allowed, an attacker who can inject HTML can inject <script> tags that execute. The CSP provides no protection against XSS.
Whitelisted CDN Domains
CSPs that whitelist CDN domains like cdnjs.cloudflare.com or cdn.jsdelivr.net are bypassable because these CDNs host JavaScript libraries that can be used as gadgets:
Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com
An attacker can load AngularJS from the whitelisted CDN, then use AngularJS template injection to execute arbitrary JavaScript. The script loads from the whitelisted domain, so the CSP allows it.
JSONP Endpoints
If your CSP whitelists a domain that hosts a JSONP endpoint, an attacker can use the JSONP endpoint as a script gadget. The JSONP response is a valid JavaScript file that executes a function with attacker-controlled data:
<script src="https://trusted.com/api?callback=alert(1)//"></script>
If trusted.com is in your script-src whitelist and has a JSONP endpoint, the CSP is bypassable.
base-uri Absence
If your CSP does not include a base-uri directive, an attacker who can inject HTML can add a <base> tag that changes the base URL for relative script URLs. Your application's scripts, loaded from relative paths, will be fetched from the attacker's server instead.
Script Gadgets in Frameworks
JavaScript frameworks often evaluate strings as code or load resources dynamically. If the framework is loaded from a whitelisted source, its dynamic code evaluation capabilities can be used to bypass the CSP.
React's dangerouslySetInnerHTML, jQuery's $.html(), and AngularJS's template compilation are all potential CSP bypass vectors when combined with injection vulnerabilities.
Building an Effective CSP
Use Nonces or Hashes
Instead of whitelisting domains, use nonces or hashes to authorize specific scripts:
Content-Security-Policy: script-src 'nonce-RandomValue123'
Each legitimate script includes the nonce:
<script nonce="RandomValue123">
// This script executes
</script>
Injected scripts do not have the nonce and are blocked. The nonce must be randomly generated for each page load, making it impossible for an attacker to predict.
Strict-Dynamic
The strict-dynamic directive allows scripts loaded by a nonced or hashed script to execute, regardless of domain whitelists. This accommodates dynamic script loading patterns without weakening the CSP:
Content-Security-Policy: script-src 'nonce-RandomValue123' 'strict-dynamic'
With strict-dynamic, your nonced scripts can dynamically create and load additional scripts. The trust propagates from the nonced parent to the dynamically loaded children.
Include All Directives
A comprehensive CSP covers all resource types:
Content-Security-Policy:
default-src 'none';
script-src 'nonce-RandomValue123' 'strict-dynamic';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
object-src 'none'
Start with default-src 'none' and explicitly allow each resource type. This ensures that new resource types added to the CSP specification default to blocked.
Report-Only Mode for Testing
Deploy new CSP configurations in report-only mode first:
Content-Security-Policy-Report-Only: ...
This logs violations without blocking resources. Use the violation reports to identify legitimate resources that your CSP would block, and adjust the policy before enforcing it.
Collect and Analyze Violation Reports
Configure CSP reporting to understand what your policy blocks:
Content-Security-Policy: ...; report-uri /csp-report; report-to csp-endpoint
Analyze violation reports to identify false positives (legitimate resources blocked) and true positives (actual injection attempts). High volumes of violations from unexpected sources may indicate active exploitation attempts.
CSP in the Supply Chain Context
CSP is directly relevant to supply chain security. When a third-party script loaded on your page is compromised, CSP determines whether the compromised script can exfiltrate data, load additional malicious scripts, or modify page content.
A CSP that whitelists the compromised script's domain offers no protection. A nonce-based CSP with strict-dynamic limits the compromise to the capabilities of the loaded script, preventing it from loading additional scripts from the attacker's infrastructure.
Subresource Integrity (SRI) complements CSP by ensuring that even whitelisted scripts have not been modified. Together, CSP and SRI provide defense against both script injection and third-party script compromise.
How Safeguard.sh Helps
Safeguard.sh monitors the third-party dependencies in your web applications, including client-side JavaScript libraries that are subject to CSP enforcement. It identifies vulnerable versions of front-end libraries, tracks which CDN-hosted scripts your applications depend on, and alerts when those scripts have known vulnerabilities. When a third-party library used in your CSP whitelist is compromised, Safeguard.sh helps you assess the impact and prioritize remediation.