Web-Security: With Content Security Policy against Cross-Site Scripting, Part 2
Extended CSP directives help to protect applications efficiently against cross-site scripting.
(Image: monticello/Shutterstock.com)
- Martina Kraus
Cross-site scripting (XSS) remains one of the most common security threats to web applications. Despite advanced protection mechanisms, attackers continue to find new ways to exploit XSS vulnerabilities. Development and security teams can use Content Security Policy (CSP) to effectively protect against XSS.
The first part of this two-part article series explained the concepts and procedures of a content security policy. The sequel clarifies advanced issues and questions.
Videos by heise
The problem with the content security policy
Modern web applications often use a large number of scripts that dynamically load additional scripts. A widget that is integrated into a web application may require additional JavaScript files that it loads independently.
For example, if a widget requires the JavaScript file https://platform/widget.js from an external content delivery network (CDN), this domain must be listed in the CSP rules in order to be allowed to load the external JavaScript file:
Content-Security-Policy: script-src 'self' https://platform/widget.js
So far, so good. However, this external JavaScript file potentially loads further JavaScript files from other CDNs. These must also be listed in the CSP:
Content-Security-Policy: script-src 'self'
https://platform/widget.js
https://platform_one/script.js
https://platform_two/index.js
This little game can go on indefinitely and result in a huge CSP rule that has to be constantly maintained.
This is the reason why many felt that the Content Security Policy Header was impractical for a long time.
Fortunately, the new strict-dynamic directive soon provided a way out of the misery.
Automatic trust propagation
The keyword strict-dynamic allows a script provided with a nonce to load and execute further scripts. This also applies if the reloaded scripts are not explicitly listed as trustworthy in the CSP rules.
(Image:Â Martina Kraus)
As shown in the illustration, the CSP rules allow the browser to load and execute both https://platform/widget.js and the required https://platform_one/script.js, provided the loaded script has been provided with a nonce. However, executing the script https://platform_one/evil.js is prohibited, as it neither has a nonce nor is loaded by a script that the browser already trusts.
Since strict-dynamic is used to combat the domain confusion in a CSP rule, it cannot be used for URLs, but only allows scripts approved with a nonce to load additional resources and execute them. A browser automatically ignores URL-based expressions in the CSP rules when it encounters strict-dynamic.
CSP nonces in a modern single page application
In order to use CSP nonces, it is necessary to calculate a new nonce value for each new request to the application and to deliver it within the CSP HTTP header. However, this requires partial server-side rendering of the application, as the calculated nonce must also appear in the script tag. With an Express application, this could look like this using the npm package EJS – Embedded JavaScript Templating, for example:
//index.ejs
<html>
<body>
<script nonce="<%= nonce %>" src="valid.js"></script>
</body>
</html>
//server.js
const csp_nonces = {
directives: {
'script-src': [NONCE] // NONCE refers to a
// freshly calculated nonce
}
}
app.get("/", expressCspHeader(csp_nonces), (req, res) => {
// Render the EJS page with the nonce
// The middleware exposes the calculated nonce on req.nonce
res.render(`index`, { nonce: req.nonce });
});
The Express middleware expressCspHeader adds a recalculated nonce value to the CSP HTTP header and also makes the value available as a request object. The application uses the latter to transfer the new nonce value to the EJS file. When rendering the HTML page, it inserts the transferred nonce value in the script tag for the placeholder.
However, if the application is delivered without a template engine and without server-side rendering, the use of CSP nonces is often not possible and CSP hashes must be used instead. Before resorting to hashes, however, it is advisable to familiarize yourself with the server-side rendering options of the framework used. For example, the front-end framework Angular has invested heavily in the development of functions for server-side rendering to make this process as simple as possible.
An alternative approach is to use Function as a Service (FaaS) such as AWS Lambda or Microsoft Azure Functions. In this case, the FaaS simply renders the index.html file using a template engine and automatically adds the necessary CSP notices in the relevant places. All other static files such as JavaScript, stylesheets or frontend files can still be provided by Content Delivery Networks without any problems.
The insertion of CSP nonces into the content security policy often requires adjustments to the server architecture in order to ensure the security of the web application.
Unfortunately, occasionally teams want to protect themselves from cross-site scripting attacks but have no way to modify the HTTP response headers of their application. In this case, some content security rules can be integrated directly into the HTML document via a meta tag to ensure at least basic protection against XSS attacks.
CSP as a meta tag
Content security policy rules can be defined not only via HTTP headers, but also in meta tags within the HTML documents of a website. This alternative method is particularly useful if you do not have direct access to the HTTP header configuration of the server or want to carry out quick tests.
To define CSP rules via a meta tag, insert a <meta> element into the <head> area of the HTML document, set the http-equiv attribute to Content-Security-Policy and specify the guidelines in the content attribute:
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
The meta tag approach increases flexibility by allowing the definition or customization of CSP policies directly in the HTML document without adjustments to the web server. In addition, the definition in the meta tags speeds up the development and testing process, as developers can quickly test various CSP guidelines without having to wait for server configurations or deployments to be updated.
Unfortunately, this also comes with some limitations in terms of security and functionality: meta tags are more susceptible to manipulation by XSS attacks, as a successful attack can make it possible to change or remove the meta tag. Furthermore, not all CSP directives can be set via meta tags.