Introduction
The Content Security Policy (CSP) is a special HTTP response header designed to mitigate certain classes of client-side attacks, most notably Cross-Site Scripting (XSS). Some engineers view the CSP as a “magic bullet” against XSS. However, if the CSP is set up improperly, these misconfigurations can be exploited by attackers to completely bypass the policy and execute malicious code.
Content Security Policy (CSP) Basics
The CSP header is straightforward and is composed of directives separated by a semicolon (;). These directives act as specific security policies applied to a website.
Key CSP Directives
These directives define what types of resources are allowed and how they can be used:
default-src: Acts as a catchall for any resource type not explicitly listed below.script-src: Describes the allowed sources for loading JavaScript files.style-src: Describes the allowed sources for loading stylesheets.img-src: Describes the allowed sources for loading images.connect-src: Applies to AJAX requests and WebSockets.font-src: Describes the allowed sources for loading fonts.object-src: Describes the allowed sources for loading objects, such as thetag.media-src: Describes the allowed sources for loading audio and video files.frame-ancestors: Describes which sites are permitted to load this site in an iframe (mitigates clickjacking).
Key Source List Values
These values define which resources can be loaded and from where:
| Source List Value | Description | Implication |
* | Wildcard: Load resources from anywhere. | Dangerous: Negates protection. |
'none' | Block All: Blocks all resources of this type. | Highly restrictive. |
'self' | Same Origin: Can only load resources from the same origin (protocol, domain, and port). | Standard and safe default. |
data: | Data Scheme: Can only load resources from the data: schema (e.g., Base64 encoded resources). | Useful but can be abused. |
https: | HTTPS Only: Can only load resources over HTTPS. | Good security practice. |
'unsafe-inline' | Inline Elements: Allows inline elements (e.g., onclick handlers, tags, javascript: URLs). | Dangerous: Re-introduces XSS risk. |
'unsafe-eval' | Dynamic Code: Allows dynamic code evaluation (e.g., the eval() function). | Dangerous: Re-introduces XSS risk. |
'sha256-...' | Hash Match: Can only load resources if the content matches the specified cryptographic hash. | Highly secure for specific inline scripts. |
'nonce-...' | Nonce Match: Allows an inline script or CSS to execute if the tag contains a nonce attribute matching the nonce specified in the CSP header. | Highly secure for specific inline scripts. |
Example CSP Header Breakdown
A CSP is typically returned in the HTTP response header.
Example:
default-src 'none'; frame-ancestors 'none'; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com
Let’s look at what some of these policies are doing:
default-src 'none';: This establishes a baseline policy to block everything unless explicitly told otherwise.frame-ancestors 'none';: This policy blocks other sites from loading this site in an iframe, effectively killing the clickjacking vulnerability.script-src github.githubassets.com;: This policy restricts the site to only load JavaScript files from thegithub.githubassets.comdomain, essentially killing traditional XSS unless a bypass can be found on that specific whitelisted domain.style-src 'unsafe-inline' github.githubassets.com: This policy allows stylesheets from one source and also permits inline CSS (which can sometimes be abused, though less so than inline scripts).
Basic CSP Bypass Due to Misconfiguration
One of the easiest ways to mess up a CSP implementation is to use dangerous source list values when setting policies.
1. Wildcard Permissions (*)
Always look out for wildcard permissions.
- Vulnerable CSP:
default-src 'self' *
Since default-src acts as a catchall and * acts as a wildcard, this policy essentially says allow any resources to be loaded from anywhere. This is the same thing as not having a CSP header at all.
2. Unsafe Inline and Eval
The use of 'unsafe-inline' and 'unsafe-eval' is always a critical danger when applied to the script-src policy.
- Vulnerable CSP Snippet:
script-src 'unsafe-inline' 'unsafe-eval' ...
If 'unsafe-inline' is present, XSS payloads that are normally blocked will execute:
- “
(or similar inline injection)
If data: is present, it allows resources to be loaded via the data URI scheme:
All of the techniques above bypass the CSP because of a misconfiguration or by abusing a legitimate but dangerous feature of CSP itself.
Advanced CSP Bypass Techniques
Beyond simple misconfigurations, attackers can find ways to bypass a seemingly strict CSP.
1. JSONP CSP Bypass
JSONP (JSON with Padding) is an older technique used to bypass the Same-Origin Policy (SOP). A JSONP endpoint allows you to insert a JavaScript payload, typically in a GET parameter called callback, and the endpoint will then return your payload wrapped in a JSON content type, allowing it to execute.
- Example JSONP Endpoint:
https://accounts.google.com/o/oauth2/revoke?callback=alert(1337)
The danger arises when a CSP header whitelists a domain that hosts a JSONP endpoint in the script-src policy.
- Vulnerable CSP Snippet:
script-src https://accounts.google.com;
If an application is vulnerable to XSS but has a strict CSP, a direct injection will be blocked:
http://example.com/?vuln_param=javascript:alert(1);(Blocked)
However, if accounts.google.com is whitelisted, the attacker can use the whitelisted domain to serve the payload:
http://example.com/?vuln_param=https://accounts.google.com/o/oauth2/revoke?callback=alert(1337)(Allowed)
The attacker abuses the JSONP feature on the whitelisted domain to load their malicious JavaScript, thus bypassing the CSP policy.
2. CSP Injection Bypass
CSP Injection occurs when user-supplied input is reflected directly into the CSP header itself.
Suppose an application URL is: http://example.com/?vuln=something_vuln_csp
And the reflected input results in a header like:
script-src something_vuln_csp; object-src ‘none’; …
If the attacker can control the value of script-src, they can easily bypass the CSP by setting this value to a domain they control (e.g., one hosting a malicious script):
- Injection Payload:
vuln=attacker.com - Resulting Header:
script-src attacker.com; object-src 'none'; ...
The CSP now allows the site to load scripts from the attacker’s domain, making XSS trivial.
Summary
The CSP is a powerful security header used to control where an application can load its resources from, often mitigating XSS and clickjacking. However, if set up improperly, it can be bypassed. Security testers must look for misconfigurations (e.g., wildcards, 'unsafe-inline'), a vulnerable JSONP endpoint hosted on a whitelisted domain, or a CSP Injection flaw where user input is reflected in the header. These methods demonstrate that even advanced security controls can be turned against themselves if not implemented with extreme care.