Content Security Policy (CSP) is a security layer that all modern browsers support with minor differences in regards to how it is being handled [1]. CSP was designed to application attacks such as Cross Site Scripting (XSS) [2] and clickjacking. Notably, such attacks have been popular for more than ten years.
A developer can employ CSP to restrict (in a whitelist / blacklist manner) the sources from which the application can receive content. Such content involves several elements:
- frame-src: which specifies valid sources for nested browsing contexts using elements such as <frame> and <iframe>.
- img-src: Valid sources for images and favicons.
- script-src: Valid sources for Javascript.
- style-src: Valid sources for stylesheets.
For a complete list of all available directives refer to the following citation: [3].
If the source of the element (e.g. script or image) is not trusted the policy will reject it on the browser’s side. Also alongside the whitelisted domains, a developer can define the protocols (or schemes as they are called) that are allowed for pulling content from the whitelisted domains.
Using CSP
Browsers that implement the CSP, expect to find an HTTP header in the response. That header is called Content-Security-Policy. Policies provided with this header are semicolon separated as we’ll see later on. Most reverse proxies provide the ability to add headers at will and this is how CSP is implemented by these web servers.
For example, for NGINX, one should add the following:
add_header Content-Security-Policy "script-src 'self'"
Some Examples
Example 1
Content-Security-Policy: default-src 'self'
The above indicates to the browser that all content coming from the current domain is allowed, for all types of content. NOTE: This excludes subdomains.
Example 2
Content-Security-Policy: default-src 'self' *.trusted.com
Expanding on the previous example, here we allow trusted.com and all of its subdomains
Example 3
Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com
A more real-world example, allows javascript from the current domain and a known CDN for pulling your angular / react js from.
Example 5
content-security-policy:
script-src https://apis.google.com/_/streamwidgets/
...
report-uri https://mail.google.com/mail/cspreport;
object-src https://mail-attachment.googleusercontent.com/attachment/ script-src 'report-sample' 'unsafe-eval' 'nonce-gTWEiUr6jJYUSdaZ17xO8Q' 'unsafe-inline' 'strict-dynamic';
object-src 'none';
base-uri 'self';
...
The above is a CSP for gmail.com. The policy is quite loose for browsers that implement CSP2 and more strict for modern browsers that support CSP3.
For more information, read up on citation [3].
Before Production
CSP is meant to break things if not implemented correctly, for example, by default inline javascript in traditional <script> tags is not allowed. For this method to work, one has to provide:
script-src: 'unsafe-inline'
Additionally, custom event handlers and JavaScript: URLs are also banned by default. On another scenario, let’s say a developer adds another javascript dependency from another CDN that is not whitelisted but forgot to update the CSP. The script will be allowed in a local/development environment where CSP might be disabled, but fail to load on the production deployment.
Since CSP is hard to get right and we do not want to deploy such policies on production without testing them, CSP provides another header that allows such testing to occur. The header is called Content-Security-Policy-Report-Only and allows us to load all content without blocking anything, but instead report on CSP violations. This is harmless for any environment that deploys a CSP policy and allows us to debug on production.
Using the above header you can either see the errors in your browser’s console, or enable reporting.
Report-uri
On both Content-Security-Policy and Content-Security-Policy-Report-Only a directive can be used to tell the browser what to do on CSP violation. The directive contains a URL where the browser will send a POST request, containing all information about the violation that occurred [4].
Caveats and hurdles
Nowadays development is rapid, and most people deploy many times during the day, putting aside whether this is the right way of doing things, some times developers need to allow unsafe-inline scripts. If that is the case, we recommend going for the nonce-based route. All that is needed for this is to provide an unguessable nonce with the script tag as shown below:
Content-Security-Policy: script-src 'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8='
This way, even if an adversary injects a script tag, they would have to guess the hash provided by the backend. Note that, the nonce gets generated in a per-request manner, otherwise it would be trivial for the adversary to make a request, find out what the nonce is and use it later on to attack the application.
As mentioned above, deploying CSP is hard [6], it needs maintenance and can be a headache for both developers and operators. Hence, a question arises, who is responsible to maintain the CSP? Developers? Operators? Security engineers? There is no clear answer as it concerns all three actors. Developers will have to decide what policy is needed for an application, as they will add and / or remove dependencies. Security engineers have to provide developers with the right information in order to help them make an educated decision and also review the CSP. Finally, operators might assist in monitoring CSP violation reports when deployed on production.
Relatively recent research has shown that CSP is nearly impossible to get right [5] and also proved that 94.72% of real-world CSP deployments could be bypassed. CSP 3 that is currently a working draft, contains new methods to implement CSP based on cryptographic nonces and not whitelisted domains. Some of the new directives are currently implemented by most browsers. One of them is strict-dynamic that says to the browser, trust only the following nonce (cryptographically secure hash) whether it is an inline or an external source.
One thing is for certain, CSP is another layer of security, and should only be considered as one. We shouldn’t rely only on CSP and deploy other measures to detect and fix the vulnerabilities although CSP can in some cases limit exploitation. So audit your codebase, fix issues and use CSP with caution.
[1] https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP#Browser_compatibility.
[2] https://cert.grnet.gr/el/cross-site-scripting-xss/
[3] https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
[4] https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only#Violation_report_syntax
[5] https://ai.google/research/pubs/pub45542.pdf
[6] https://uselesscsp.com/