Aaron Peters

CSP Header – An antidote to XSS

7 min read

In this post we'll be looking at why and how we implemented a Content Secruity Policy header in eco.banking.

What are XSS attacks?

In an XSS attack the attacker tries to run malicious script(s) in his victims browser to steal credentials, data or execute tasks on his behalf.

There are different types of XSS attacks:

Stored XSS

Stored XSS occurs when malicious scripts are directly saved on the target server, such as in a database, message forum, visitor log, or comment field. The script is then executed in the browser of any user who accesses the affected page or resource. Since the attack is stored, it can affect multiple users and persist over time until it is discovered and removed.

An attacker could for example enter a script as his username which will be stored in the DB and could be displayed in a comment section for example which would execute the script on the viewers client.

Reflected XSS

Reflected XSS attacks involve crafting a URL or request that contains malicious JavaScript. When the victim clicks on the URL or the malicious request is sent to the server, the server includes the script in the response to the user. The user's browser then executes the script. Unlike stored XSS, reflected XSS targets individual users and requires some form of interaction (like clicking a link) to trigger the attack.

An attacker could for example craft a link with an urge to click, which will redirect to a serious website but include a script e.g. in the URL or payload which will be reflected by the serious server. If the reflected script is displayed on the page, it can be executed.

DOM-based XSS

DOM-based XSS is a form of XSS where the vulnerability exists in the client-side code rather than the server-side code. The attack payload is executed as a result of modifying the DOM (Document Object Model) environment in the victim’s browser. This type of XSS attack occurs when the web application’s client-side scripts write data provided by the user to the DOM without proper sanitization, allowing an attacker to execute arbitrary JavaScript in the victim’s browser.

An attacker could for example craft a link with an urge to click, which will redirect to a serious website but include a script in the query params for example. If these query params are displayed on the page, the included script could be executed.

The difference here is that with DOM-based XSS the vulnerability lies on the client side and with reflected XSS it lies on the server side.

How a CSP header helps

A CSP header helps in these cases as it restricts sources from which scripts are allowed to be executed as well as other potentially dangerous sources, for example inline styles or frames.

There are different versions of the CSP header which all support different policies.

In general the CSP header has a very good browser compatibility.

Please have a look at the official docs for more information.

Implementing a CSP header

Header structure

The header content itself consists of directives and one or more sources allowed for this directive:

Content-Security-Policy: <directive> <source>; <directive> <source>

All the different policies can be found here

There are multiple types of sources that can be used but the most important ones are 'self' for the current origin and full hosts like https://be.eco-banking.dev.fnstrt.de.

Header location

The header can be placed in two different locations. As a meta tag or as a response header. The response header being created for example on nginx is safer and allows for more configuration possibilities but is a bit more complex to set up depending on the CSP header needs (nonce source needed or not).

Meta tag example:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'" />

Response header example:

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

Reporting or blocking

For existing projects it makes sense to choose between enforcing the implemented policy-directives or doing a incremental enforcement with a reporting period.

There is a special Content-Security-Policy-Reporting-Only header which has the same stucture as the CSP header but tells the browser to not enforce the defined policies but catch any cases that would have blocked some source and send a JSON POST request to the URI specified in the report-url policy-directive.

Sample payload:

{
  "csp-report": {
    "document-uri": "http://example.com/page.html",
    "referrer": "",
    "blocked-uri": "http://example.com/script.js",
    "violated-directive": "script-src 'self'",
    "original-policy": "default-src 'none'; script-src 'self'; report-uri /csp-violation-report-endpoint/",
    "disposition": "report"
  }
}

Example implementation steps

  1. Create the CSP header either in a meta tag or as a response header.
  2. Set the default-src directive (which acts as a fallback for most of the directives) if possible to general trusted sources or if this does not apply to 'self'.
  3. Either: a. try to move through the application, observe any errors that come up pointing out a policy-directive blocking a source from being loaded and add the source to the header. b. or use the reporting only header instead, set up an endpoint to collect the reports and make changes during a reporting period to make sure your policy is not too broad nor too narrow.

Nonces and hashes

Nonces

Nonces are used to allow certain sources (e.g. scripts and tags) specifically. The nonce added to the respective tag has to match with the nonce defined in the policy-directive the source is part of.

When implementing the CSP header directly on the response from the server serving the FE we need to make sure the nonce is accessible both in the CSP header as well as in the FE application where we need to add the nonce to a script tag e.g. The actual implementation depends heavily on the environment.

Hashes

Hashes are used to allow certain sources based on their content. When an CSP error is thrown it provides a hash that can be added to the CSP header to allow this particular content.

Third party libraries

Third party libraries can be a bit cumbersome. In our eco.banking example this was the case with Styled Components and Emotion. They are both using inline styles which are generally a bad practice security-wise but since we can't change their source code and add a nonce we have to find another way. A hash also isn't viable since we would have to add lots of those and a change in the source code would change the hash anyway. Thus the only viable option in this case was to allow 'unsafe-inline' for the style-src directive and make sure that our own source code does not include any (best case or no un-sanitized) style tags/props using eslint rules.

Some points to keep in mind

  • The sources do not take any URLs with paths
  • There are special sources such as 'self' which need the single quotation marks
  • Nonces should be re-generated on each request