Content Security Policy - Why You Need It
This is the first in a series of blog posts about server headers
Heads up! We’re about to launch WASP, a Web Application Security Platform. The aim of WASP is to help you manage (well, you guessed it) the security of your Posit Connect application using Content Security Policy and Network Error Logging. More details soon, but if this interests you, please get in touch.
This blog post is aimed at those who are somewhat tech literate but not necessarily a security expert. We’re aiming to introduce the concept of Content Security Policy and teach some of the technical aspects.
In 2018, a hacking group called Magecart exploited a vulnerability on the British Airways website that allowed them to inject JavaScript. The JavaScript code was used to send customer data to a malicious server, succeeding in skimming the credit cards of 380,000 transactions before the breach was discovered. This type of attack comes under the umbrella of cross-site scripting (XSS) - where malicious code (often client-side JavaScript) is injected into the browser.
What is Content Security Policy?
Content Security Policy (CSP) is
a framework of modern (ish) browsers, that allows a developer to protect
an application through the use of the Content-Security-Policy
HTTP
header. It’s used to give applications an extra layer of security -
safeguarding against attacks such as cross-site scripting. In this blog
we’re going to take you through some of the basics of Content Security
Policy and show you why it’s a necessity for modern applications.
How will Content Security Policy help me?
In one way or another, you have made it to this blog post on jumpingrivers.com. This means your browser has already loaded a tonne of assets that this page needs to look and act in the way it does (JavaScript, fonts, stylesheets). Without CSP, the browser will trust and not question any loaded resources from any source. If there are any vulnerabilities with this page, an attacker could run client-side JavaScript to import content hosted from their own source; for instance, a fake form or a malicious click event to skim user details or steal data from a database, just like with British Airways. Your browser simply says “Yes, why wouldn’t I trust this code?”. This is where CSP comes into play.
How does CSP link to R?
Have you ever used the {shiny}, {quarto} or {rmarkdown} R packages to make web applications or documents? If you then took the extra step to deploy your app, you should be asking the question “How safe is it to deploy this?”. {shiny}, {quarto} and {rmarkdown} pull in a lot of external resources; css, JavaScript etc. This leaves them vulnerable to cross-site scripting attacks, just like British Airways. Using CSP, we can protect our {shiny} / {rmarkdown} documents against these attacks.
The technical basics
A Content Security Policy HTTP header is set on the server side, but
protects the client side. A CSP header is split into directives - each
directive enabling you to specify an allow list (in some cases, a deny
list) of valid sources for content that the browser can (or is not
allowed to) load. For instance, one of the more common directives,
script-src
, allows us to specify valid sources for scripts. Any
scripts that are from a source not listed within this directive will be
blocked from executing in the browser. A basic CSP header using
script-src
might be
Content-Security-Policy: script-src 'self'`
The metasource, self
, is telling the browser to allow scripts to be
loaded from our domain. As there are no others sourced listed with it,
we are telling the browser to only allow scripts to be loaded from
our domain. There are other metasources:
'self'
: Content from the same domain,'none'
: Nobody can include this functionality. In the case above, this would mean we accept scripts from no sources.- A nonce / hash: Accept code with a specific nonce / hash.
Of course, we can also specify specific URL / domains. For instance,
Content-Security-Policy: script-src 'self' https://posit.co/
would allow loading of scripts from our own domain, and Posit. Other common directives include
default-src
: Default values for*-src
directives.font-src
: Valid sources for fonts loaded using the@font-face
CSS at-rule.frame-src
: Valid sources for embedded frame contents.img-src
: Valid origins from which images can be loaded.navigate-to
: Restricted URLs from which a document can initiate navigation.style-src
: Valid sources for stylesheets.media-src
: Valid sources for loading media using
For a full list, see the MDN Web Doc.
Reporting Content-Security-Policy violations
If an attacker had found any vulnerabilities on our site, then using the directives above we would be blocking a good bunch of potential attacks for users on modern browsers. However, users on browsers (mainly Internet Explorer) that still do not support the CSP directives you’ve chosen are still at threat. It’s important that we understand which CSP directives are being targeted on our site, to protect the vulnerable on old browsers.
Directives are split into two categories; blockers and reporters.
Blockers block input into the application (think script-src
) and
reporters deliver reports about the blocks. This allows us to understand
which of our CSP directives are being targeted.
The most important reporting directive is report-to
. However, it’s
predecessor, report-uri
, still plays a crucial role. In fact, all
browsers will fall back to report-uri
if it can’t find report-to
.
We’ll go into more detail on the differences between the two in a later
blog, but for now we’ll look into report-uri
(it’s a tad simpler).
The report-uri
directive allows us specify the URL(s) to which our CSP
violation should be reported. These URLs are usually API endpoints,
which process the report JSON. The following HTTP header would POST any
violations to the csp-reporting
endpoint on our domain
Content-Security-Policy: script-src 'self'; report-uri /csp-reporting
Any reports sent to this endpoint will be
Content-Type: application/reports+json
and contain four important
pieces of information (plus some others):
blocked-uri
: URI of the blocked resourcedocument-uri
: URI of the document in which the violation occurredoriginal-policy
: The original Content Security Policyviolated-directive
: The CSP directive that was violated
The format will look something like
{
"csp-report": {
"document-uri": "https://magecart.com/example.html",
"referrer": "",
"blocked-uri": "https://badwebsite.com/css/style.css",
"violated-directive": "script-src 'self'",
"original-policy": script-src 'self'; report-uri /csp-reporting",
"disposition": "report"
}
}
This report indicates that on the page magecart.com/example.html
,
something has tried to load the style file located at
badwebsite.com/css/style.css
. However, because we have the
script-src
directive set to "self"
, only scripts from our own domain
may be sourced.
Some limitations
Whilst CSP is a great addition to the security toolbox, there are some “limitations”:
- It’s not a magic wand. It’s a control to cut down on your application’s exposure - it will not patch vulnerabilities. Think of it like a firewall - it’s a secondary control, a defence technique. Mostly in case the developers have missed something. If you’re having trouble with any security issues, feel free to get in touch with us for advice.
- It’s only useful for client-side attacks on your application. It does not help with server-side, database attacks or anything in between.
- OK, this last one isn’t really a limitation, more of a warning. It’s
not on by default. The
Content-Security-Policy
HTTP header has to be added manually with each policy individually specified.
If Content Security Policy or Shiny app security in general interests you or you want more news on WASP, our new Web Application Security Platform, then please email hello@jumpingrivers.com and we can discuss how to set this up for your applications.