Content Security Policy (CSP) is an essential security measure for protecting web applications against certain types of attack. By defining strict rules on the resources that a browser can load, a CSP limits potential attack vectors.
However, a poorly configured Content Security Policy can be bypassed, leaving the application vulnerable.
This article explores the principles of Content Security Policy (CSP), the key directives and the common misconfigurations used to bypass them. We will also look at how to test their effectiveness and apply best practices to strengthen their security.
Comprehensive Guide to Content Security Policy (CSP)
- What is Content Security Policy (CSP)?
- What are the Main Directives of a Content Security Policy?
- Possible Values for Fetch Directives
- Example of a Content Security Policy
- Configurations and Techniques for Bypassing a Content Security Policy
- How to test the effectiveness of a Content Security Policy?
- Best Practices for Strengthening the Security of a Content Security Policy
What is Content Security Policy (CSP)?
A Content Security Policy (CSP) is additional protection that helps to detect and limit certain client-side attacks, such as Cross Site Scripting (XSS), clickjacking and content injection.
It is configured at server level. Administrators define rules that browsers must respect when a page is loaded. For example, they can specify which sources are authorised for scripts, images and other resources.
There are two ways of configuring a CSP:
- Add a
Content-Security-Policy
header in server responses. - Use the HTML element
<meta>
in application pages.
What are the Main Directives of a Content Security Policy?
A Content Security Policy is made up of rules called directives. Each directive defines specific restrictions on the loading and execution of certain resources.
There are several types of directives. Fetch directives tell the browser how to load data. Other directives control browsing, documents and reporting.
Fetch directives
These directives define the authorised sources for different HTML resources:
- default-src: Default source if no other directive is defined.
- script-src: Valid sources for JavaScript and WebAssembly scripts.
- script-src-elem: Valid sources for
<script>
tags. If absent,script-src
is used. - frame-src: Valid sources for
<frame>
and<iframe>
. - img-src: Valid sources for
<object>
,<embed>
and<applet>
. - style-src: Valid sources for style sheets.
- font-src: Valid sources for fonts.
Other important directives
Some directives do not concern resource recovery but add additional security controls:
- sandbox: Activates a sandbox to isolate certain content (as for
<iframe>
). - require-trusted-types-for: Enforces the use of ‘trusted types’ to limit DOM-based XSS attacks.
- trusted-types: Defines a whitelist of ‘Trusted Types’ to prevent the execution of spoofed data.
- upgrade-insecure-requests: Converts HTTP requests to HTTPS automatically. Useful for modernising a site with many old URLs.
- frame-ancestors: Restricts the permitted sources for
<frame>
,<iframe>
,<object>
,<embed>
and<applet>
elements. - form-action: Controls the URLs allowed for sending forms.
- base-uri: Restricts the valid sources for the
<base>
tag.
These directives enhance the security of web applications by limiting common attack vectors.
Possible Values for Fetch Directives
Each fetch directive can be associated with different values to define the authorised sources. Here are some of the possible values and their effect:
none
: Completely blocks the resource.self
: Only authorises loading from the same origin as the document.[host-source]
: Specifies a valid URL or IP address, for examplehttps://vaadata.com
.[scheme-source]
: Authorises a specific scheme such ashttps:
,http
:,ws:
,data:
, etc.*
(wildcard): Allows all legal values for subdomains, hosts or ports, for examplehttps://*.vaadata.com
.nonce-[nonce_value]
: Uses a random value (nonce_value
) generated by the server for each HTTP response. Each targeted HTML tag must include this nonce so that the browser can check its validity before executing the script or style.unsafe-eval
: Allows JavaScript to be executed via functions such aseval()
,Function()
, orsetTimeout(‘code’)
. By default, this option is disabled ifdefault-src
orscript-src
is defined.unsafe-inline
: Allows execution of embedded scripts (<script>
) event attributes (onclick
, etc.), and URLs in javascript:. By default, this option is disabled ifdefault-src
,script-src
orstyle-src
is defined.
These values directly influence the security and permissiveness of a CSP. Misconfiguration can expose an application to XSS attacks.
Example of a Content Security Policy
Now that we’ve seen the directives and their values, let’s take a concrete example to better understand how a CSP works.
When a web server sends a page to the browser, it can include a Content Security Policy in the HTTP response. Here’s an example:
Request sent to the server
GET / HTTP/2
Host: whatever.vaadata.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0
Server response
HTTP/2 200 OK
Content-Type: text/html
Strict-Transport-Security: max-age=63072000
Content-Security-Policy: default-src 'self'; script-src 'self' https://*.vaadata.com; object-src 'none'; img-src 'self' data: *.vaadata.com;
X-Frame-Options: DENY
Server: Nginx
Content-Length: 10236
Vary: Accept-Encoding
X-Cache: miss
[…TRUNCATED DATA…]
In the previous response, the Content Security Policy applied to the https://whatever.vaadata.com
site page defines several security rules. Here is how they are interpreted:
default-src 'self'
: Any fetch directive not explicitly defined will use this default value. Here, only resources fromwhatever.vaadata.com
are allowed.script-src 'self' https://*.vaadata.com
: Only HTTPS sourceswhatever.vaadata.com
andvaadata.com
subdomains can load JavaScript.- A script loaded from
https://bonjour.vaadata.com
will be authorised. - A script loaded from
https://hello.vaadata.at
will be blocked, as it does not belong to thevaadata.com
domain.
- A script loaded from
object-src 'none'
: The use of <object> and <embed> tags is totally forbidden.img-src 'self' data: *.vaadata.com
:- Images can be loaded from
whatever.vaadata.com
(self
). - The
data:
schema is allowed, allowing the integration of Base64 images. - All images from
vaadata.com
subdomains are accepted.
- Images can be loaded from
This configuration restricts external sources to reduce the risks of injecting and loading malicious content.
Configurations and Techniques for Bypassing a Content Security Policy
A poorly configured CSP can contain exploitable vulnerabilities. These errors can be used to bypass protection and execute malicious JavaScript via XSS or content injection attacks.
Here are some common exploitation scenarios.
Unsafe-inline JavaScript authorisation
Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline';
The problem here is that inline JavaScript is allowed. In fact, if an XSS flaw is present, the following payload would work:
<script>alert(1);</script>
Unsafe-eval authorisation
Content-Security-Policy: default-src 'none'; script-src 'unsafe-eval' data:;
In this case, the script-src
directive authorises the execution of potentially dangerous code by allowing the use of functions that evaluate text as JavaScript.
In addition, the presence of data: in the CSP allows the inclusion of scripts encoded in Base64, which will be interpreted directly by the browser.
The following payload enables this restriction to be circumvented and JavaScript code to be executed:
<script src="data:;base64, YWxlcnQoMSk="></script>
If the script-src
directive did not allow data:
, it would be impossible to execute a script directly in this way. However, the application would still be vulnerable to a DOM XSS attack if a user-controlled parameter was used in a function such as eval()
.
Use of a wildcard in script-src
Content-Security-Policy: default-src 'none'; script-src https://vaadata.com *;
In this example, the script-src
directive contains a wildcard (*
), which means that scripts can be loaded from any source. This parameter overrides any restrictions potentially put in place by other rules defining specific URLs.
An attacker could exploit this vulnerability by hosting a malicious script on a server under his control, such as evil.vaadata.at
, and include it on the targeted page using the following payloads:
<script src="https://evil.vaadata.at"></script>
<script src="data:;base64, YWxlcnQoMSk="></script>
Absence of object-src and default-src directives
Content-Security-Policy: script-src ‘self’; img-src ‘self’; img-src 'self’;
In this example, although the script-src
and img-src
directives are defined to limit the authorised sources to the same origin (‘self’
), the object-src
and default-src
directives are missing. This allows an attacker to exploit this configuration by using an <object>
element to inject malicious code.
The following payload can be used to bypass the CSP and perform an XSS attack:
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>
This method works because the <object>
element is not restricted by the security policy, allowing the JavaScript code contained in the Base64-encoded data to be executed.
Exploitation of an authorised JSONP endpoint
Content-Security-Policy: default-src 'none'; script-src https://hello.vaadata.com/test.js
https://accounts.google.com/o/oauth2/revoke;
This Content Security Policy bypass technique is based on the exploitation of JSONP endpoints present in authorised domains. These endpoints make it possible to bypass the same-origin policy and load data from other origins, while injecting JavaScript into the response in the form of JSON.
In the example given, the URL https://accounts.google.com/o/oauth2/revoke
corresponds to a known JSONP endpoint. By sending a request with a callback parameter, the server responds with JavaScript, making it possible to inject a malicious payload. Here is an example of a request that triggers this JavaScript response:
URL of the request:
https://accounts.google.com/o/oauth2/revoke?callback=alert(1)
Request:
GET /o/oauth2/revoke?callback=alert(1) HTTP/2
Host: accounts.google.com
Response:
The returned response contains executable JavaScript, which allows the alert(1)
function to be executed via a script loaded from this endpoint. The following payload thus bypasses the CSP:
<script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1)"></script>
It is therefore essential to check whether JSONP endpoints are present in the domains authorised by the CSP, as these entry points may allow malicious JavaScript to be executed.
Bypassing CSP via file uploads
Content-Security-Policy: default-src 'self';
In this case, the CSP seems secure, as it limits the authorised resources to those from the same origin as the document.
However, the CSP could be circumvented via another feature present on the platform, such as a file upload feature. If this functionality allows the upload of text files or JavaScript code, an attacker could upload a file containing a malicious payload.
While there are restrictions on the types of file that can be uploaded, there may be ways around this.
Once the malicious file has been uploaded, the attacker could reference this file via a relative path and include it in the page via a <script> tag as follows:
<script src="/upload/evil-script.js"></script>
How to test the effectiveness of a Content Security Policy?
To assess whether your CSP configuration is optimal, Google provides a tool for identifying configuration problems that could affect security: CSP Evaluator.
Let’s take the following policy as an example:
Content-Security-Policy: default-src 'self'; script-src 'unsafe-eval' *; script-src-elem https://accounts.google.com/*; img-src ‘unsafe-eval’ data:;
By submitting it to CSP Evaluator, we obtain a detailed analysis of the directives in place.
The tool highlights any associated risks and indicates, in blue, any missing directives that are essential for enhancing security.
By following the recommendations provided, you can improve your CSP to make it more robust and secure.
Best Practices for Strengthening the Security of a Content Security Policy
As we have seen, the configuration of content security policy can sometimes be circumvented depending on the specifics of the application. It is therefore essential to understand that the security of an application should not be based solely on these policies.
CSPs provide an additional layer of protection (defence in depth) that can limit or prevent the exploitation of vulnerabilities such as Cross-Site Scripting (XSS) or content injection.
The first line of defence against these attacks remains input validation and output encoding.
In general, the following points should be respected in your Content Security Policy:
- Define a restrictive default-src directive.
- Apply a strict object-src directive (ideally
none
). - Avoid the values
unsafe-eval
andunsafe-inline
. As a general rule, ban directives containing ‘unsafe’, unless their implications are fully understood. - Do not allow scripts to be loaded from external servers.
- Minimise the use of wildcards (
*
). - Use nonces to reinforce security.
- Assess the robustness of your CSP using specialised tools (see previous section).
Author: Yoan MONTOYA – Pentester @Vaadata