Imagine this: you've just had a penetration test conducted on your application. All identified vulnerabilities have been neatly resolved, yet your security analyst continues to insist on implementing something called a 'Content Security Policy' (CSP). Or maybe you already have a CSP, but it’s claimed that it isn’t secure enough. Why spend extra time and resources on something you thought was already under control? Let’s dive into a bit of history.
The Importance of Security Headers
When browsers were first designed, no one could have anticipated what kinds of attacks would eventually become possible. Some of the attacks we know today could potentially be eliminated, or at least limited, by changing how browsers work. However, altering how browsers function would significantly impact existing internet services, making it not a feasible option. The alternative? We provide users with higher security needs the ability to add extra layers of protection, known as security headers. When a site is visited, the server responds with an HTTP response header. This is an additional instruction for your browser, making your portion of the internet—your website or web application—safer.
What is a CSP?
Content Security Policy, or CSP for short, is an example of such a security header [1,2]. The idea is simple: a CSP restricts what a browser can do with content and where that content can originate. In the default setting, there are few restrictions, meaning a website can load content from nearly any source on the internet. This includes seemingly innocuous things like images, but also JavaScript code that executes directly in the browser. With a CSP, you as the website owner determine exactly where content can come from and under what conditions.
Think of the CSP as your second line of defense. If an attacker manages to find a vulnerability, the CSP makes it much harder for them to exploit it. Let's consider a concrete example.
The Danger of Cross-Site Scripting
Frontend content and code injection vulnerabilities have long been a known problem. This is a class of vulnerabilities where data, such as text submitted by a user, is not properly processed by the application. This allows an attacker to bypass security and add their markup (HTML), styling (CSS), or client-side code (JavaScript) to the web application. This is especially dangerous with JavaScript, as it allows an attacker to execute client-side code in the victim’s browser. This is known as cross-site scripting (XSS) and gives an attacker full control over the application as though authenticated as the victim.
You might think, "But if my application isn't vulnerable to XSS, doesn't a CSP add no value?" Are you truly sure you're not vulnerable? XSS may be one of the 'oldies' when it comes to attack methods, yet it remains an issue affecting many organizations. More than 30% of the applications tested by Computest Security in the past year were vulnerable to some form of XSS [3]. Keep in mind that Computest Security's average client is already highly security-conscious, regularly conducting security tests on their applications. Therefore, the actual number of XSS-vulnerable applications is likely even higher. It's no luxury to set a strong CSP. It can provide that extra bit of security you need to mitigate unknown risks.
How Does CSP Work?
As with every interesting question, the answer is: it depends. In this case, it depends on your application and how it is structured. Let’s look at some examples of possible CSPs returned as HTTP response headers.
Focusing on the script-src directive, which handles all JavaScript sources such as *.js files or inline code between <script> tags, this directive protects you against vulnerabilities with the greatest impact.
If your application (https://example.com) is set up such that all used JavaScript is sourced from a dedicated domain (https://js.example.com), then a CSP protecting you against many types of XSS attacks might look like this:
Any script loaded from another source will not execute. The same applies to any inline JavaScript. If scripts also need to be included from the same domain hosting the application, it could look like this:
Allowing inline JavaScript makes it slightly more complex as a browser can't distinguish between legitimate and malicious injected content. Still, this is possible using nonces or hashes. A nonce, a sufficiently random value, can be included in the CSP header like so:
This value must match the value specified in a <script> tag in the same response.
If the proper nonce isn't included, the code doesn't execute. The nonce value changes with each request, preventing an attacker from predicting it in advance, rendering any potential XSS vulnerability unexploitable. An alternative method is using a hash value, calculated over the full content of the <script> tag, ensuring unmatched hashes mean no execution. This prevents an attacker from injecting their malicious code.
If you didn't develop your application with a CSP in mind, choose a solution matching your architecture. Fortunately, libraries exist for various web application frameworks to aid implementation.
The examples provided here don't showcase all possibilities of a CSP. It allows detailed configurations for all potential resources in an HTML page. Interested? Links at the end of this blog offer further reading material.
Implementation Mistakes with a CSP
Be cautious, things can go wrong when implementing a CSP. Particularly when using the whitelisting approach of sources, pitfalls exist. At Computest Security, we've often seen that one misconfigured CSP becomes useless or easily bypassed by an attacker. Examples of such errors include:
- If you include 'unsafe-inline' in the script-src directive, this allows all inline JavaScript. As the name suggests, this presents a high risk, negating most benefits of a CSP.
- When files can be uploaded to and downloaded from the application domain, there's a high risk this can be exploited to bypass a CSP allowing ‘self’ in the script-src directive, making your application vulnerable.
- If you allow a CDN or public source host like AWS S3 (e.g., https://s3.eu-central-1.amazonaws.com), an attacker can use these services to host their malicious exploits. Anyone can create an AWS account to host resources on S3, thus posing potential risks.
- If using nonces, they must be sufficiently random and used only once. Otherwise, an attacker can reuse the nonce for their attacks.
- If you allow inline JavaScript with a nonce value, the CSP doesn't protect against JavaScript injection directly in the script. A <script> tag approved with a nonce value will always execute regardless of its content.
- Permitted hosts with JSONP endpoints can be exploited by attackers to partly bypass a CSP by misusing the callback functionality, potentially allowing arbitrary JavaScript execution.
Due to these common misconfigurations, a nonce- or hash-based CSP combined with 'strict-dynamic' is generally deemed the most secure approach [4]. This allows you to specify precisely which scripts may be executed without needing to whitelist the entire host. An example of how this might appear:
Additional content can then be included within the HTML:
A well-thought-out and strict Content Security Policy precisely determines what can happen in the browser when your application loads. While a strong CSP doesn't ensure your application is fully protected from all external attacks, it sets you on the right path to a safer online environment.
Interested to see if your organization would benefit from a CSP? We're here to help! Contact us via info@computest.nl or call +31 (0)88 733 13 37 and we'll get back to you promptly.
For more reading on CSP:
[1] https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/CSP
[2] https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html