Content security policy (CSP) is a browser feature that can help minimize the risk of a multitude of security threats. To be noted that it’s one of the most exhaustive header that lets you control a various sources and parameters.
In web development, it may cause more headaches than not, because usually if it’s being enforced as strictly as possible. And implementing it later on in the project may be exponentially harder to implement than early on.
Content security policy is effectively mitigating cross site scripting. One of the prevalent injection at OWASP top. Enforcing strict Content Security Policy may have been the leading factor for OWASP top 10 ‘positive improvements’.
Here is a basic example of CSP implementation:
1 |
Content-Security-Policy: default-src ‘self’; img-src ‘self’; style-src ‘self’ google.com |
What we did here effectively is restrict or allow only certain origins to load. Mostly allow only ‘self‘ or origin requests, and in style-src‘s case google.com as an addition.
CSP in action
Let’s dive deeper:
default-src let’s you define the policy for multitude of resources, and the rest all overwritten. To be mentioned, not all directives are set as a fallback to default-src, but most of them are.
Let’s see how a real-world scenario of CSP would look like, in a JavaScript file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
const contentSecurityPolicy = { 'default-src': ["'self'"], 'object-src': ["'self'", "data:"], 'script-src': ["'self'"], 'img-src': ["'self'"], 'style-src': [ "'self'", "'unsafe-inline'", "https://fonts.googleapis.com" ], 'font-src': [ "'self'", "https://fonts.gstatic.com", "data:" ], 'frame-ancestors': ["'none'"] }; const generateCSPString = (policy) => { return Object.entries(policy) .map(([key, values]) => `${key} ${values.join(' ')}`) .join('; '); }; const cspString = generateCSPString(contentSecurityPolicy); // Example usage: const setCSPHeader = (response) => { response.setHeader('Content-Security-Policy', cspString); }; |
This is something closest to reality but not the most strict policy. And how we will explore further on in a new article, it’s not the safest policy, to rely on URL’s and ‘self’. It’s certainly better than nothing, so for now we will keep it as it is.
If everything is too complicated right now, don’t worry. We will examine each directive and discuss it.
- base-uri directive restricts the URLs, which can be used in a document’s element. If this value is absent, then any URI is allowed.
- object-src specifies restriction to object and embed elements. Altough not that important, it includes browser plugins as well, ActiveX controls, even flash or java.
- script src specifies valid sources for Javascript, not apply just to tags but also for url loaded or script event handlers.
- style-src as the name shows, specifies directives for valid css sources.
- img-src specifies directives for valid images sources.
- font-src again, as the name shows, specifies directives for valid font sources.
- frame-ancestors This specifies valid parents that may embed a page using iframe. Usually the safest is to not allow any to be loaded. Setting this directive to ‘none’ is similar to X-Frame-Options: deny (which is also supported in older browsers).
If any of script-src, style-src, object-src, img-src is absent, the user agent will look for the default-src directive for that option, instead.
These are the directives, and now the options inside them vary:
For example for setting css-src: ‘self’ ‘unsafe-inline’, https://font.googleapis.com
we are letting the browser know that we accept only css only from site origin source and font.googleapis.com. In case we have a different external source, or if we don’t load from the most popular google CDN we can remove the link allowing only css from origin.
unsafe-inline
states that we allow for css to be embedded and run inline in the DOM. Usually as a security best practice we should never use unsafe-inline, or unsafe-eval. This should be even more strict when discussing script-src and object-src, to make sure that we don’t allow for to run embeded JavaScript or various inline-scripts.
How about running scripts from browser extensions?
Unfortunately, CSP may not fully control what happens outside of it’s scope. To put it briefly, browser extensions/plugins are running with elevated privileges and can bypass CSP restrictions.
Keep in mind that CSP is primarily focused on web content security.
Conclusions
Content Security Policy is the most impactful header that can help improve the security or a web application. But it shouldn’t be the only security measure. Usually as strict as possible means not using unsafe- anywhere in the script, and also not relying on domains to allow scripts.
If a frontend site uses libraries like css-in-js or styled components, which add inline styles to specific DOM elements, developers typically add unsafe-inline
. So most times, removing unsafe-inline from style-src may be a complex task, and resources can better be used to improve other security aspects of the web applications.
Alternatives are to use encripted keys or hash for the needed elements. We will provide an detail article for that.
In this series we will continue with most important headers, and we may prepare a web app that helps scan your website and points out that’s vulnerable in terms of headers.
Photo by Bernd Dittrich on Unsplash.