Yep, you heard it right, we have a few new security features and even some new Security Headers in town! Whilst technically only COOP, COEP, CORP and CORB are new, CORS is very related despite having been around for a while and is still very much worth talking about, so let's go.
Let's set some expectations
That is a lot of new stuff to talk about and the purpose of this blog post is not to do a huge technical deep-dive into each of these features and the reasons they exist. Those posts will come in the future, so do check back for them, but for now my goal is to give a brief explanation of why they exist, outline the benefits of each of these features, give some examples of where/why they're useful so you can decide if you need them and to simply raise awareness that they do exist. Here are the full names of the features we're going to be talking about:
COEP: Cross Origin Embedder Policy
COOP: Cross Origin Opener Policy
CORP: Cross Origin Resource Policy
CORS: Cross Origin Resource Sharing
CORB: Cross Origin Read Blocking
What are these new headers for?
You may have heard of attacks like Spectre and Meltdown which rightly gathered a huge amount of attention at the time they were discovered. I'm not going to cover the technical details of those attacks, there are countless great resource online for that, but we need to know is that these side-channel attacks could allow access to data that should not have been accessed. Our concern on the Web Platform was Spectre which could allow data to be read cross-origin, breaking the Same Origin Policy.
To do this, the attacker needed the victim to have a CPU that supported speculative-execution (most CPUs), a CPU with shared cache (most CPUs) and a high resolution clock that can measure latency in nano seconds. We were never going to lose speculative-execution or shared cache because they are enormous performance wins so the initial response was to lose clocks or anything that could be used as a clock. That meant saying goodbye to powerful browser features like SharedArrayBuffer
, Performance.now()
and the JS Self-Profiling API, so we needed a way to get them back and mitigate other similar or related attacks.
Cross-Origin Isolated
If you decide to opt your site in to being 'cross-origin isolated' you get to use the powerful features lost as a result of mitigating the Spectre attack and to shield information in your origin (like cookies, passwords or credit card numbers) from being stolen in a similar manner. With cross-origin isolation, by spawning a single renderer process per site, we can ensure that side channel attacks like Spectre, or the exploitation of a vulnerability in the renderer, will not yield sensitive information from our site to an attacker.
To configure, test and eventually enforce cross-origin isolation properly, there are a selection of HTTP Response Headers at your disposal that we're going to take a look at and explain.
COEP: Cross Origin Embedder Policy
The COEP header allows you to make sure that any cross-origin resources loaded by your page are explicitly permitted to be loaded with either CORS or CORP, or they will be blocked from loading.
Cross-Origin-Embedder-Policy: (unsafe-none|require-corp); report-to="default"
As you can see, there are only 2 supported values for the COEP header, unsafe-none
and require-corp
, and that the header supports reporting via the Reporting API.
Using unsafe-none
is the same as not defining a COEP header and is the default value. In this state, the page can load cross-origin resources without explicit permission via CORS or CORP. If you specify the require-corp
value then a page can only load resources from the same origin or cross-origin if the other origin allows it via either:
- CORS - permission granted via a permissive ACAO header.
- CORP - permission granted via a permissive CORP policy.
Given that COEP could block assets from loading, it's a good idea to leverage the Report Only version of the header coupled with reporting via the Reporting API first to ensure that no harm will be caused to your site.
Cross-Origin-Embedder-Policy-Report-Only: (unsafe-none|require-corp); report-to="default"
COOP: Cross Origin Opener Policy
The COOP header allows you to break out of the Browsing Context Group for your page and ensure you do not share one with a potentially hostile origin. There are configurable levels of co-existence within COOP but by ensuring isolation in a new process between your page and a potentially hostile page, an attacker cannot have access to memory that's of interest to them.
Cross-Origin-Opener-Policy: (same-origin|same-origin-allow-popups|unsafe-none); report-to="default"
When setting the same-origin
value, our page can only share a browsing context group with other pages from our own origin that have also set a COOP header with same-origin
. You can see here that b.example
is opened in a new browsing context group because is it not the same origin as a.example
.
When using same-origin-allow-popups
our page retains references to same-origin popups with unsafe-none
either set explicitly or by default due to not having a policy, or that also have same-origin
.
The final value of none
will opt you out of COOP protections and is the default if no policy is set. This allows your page to be placed in the same browsing context groups as other pages unless they have protected themselves by enabling COOP.
As with COEP, COOP also supports a Report Only policy mode and can dispatch reports via the Reporting API for safe testing before deployment or enforcement.
Cross-Origin-Opener-Policy-Report-Only: (same-origin|same-origin-allow-popups|unsafe-none); report-to="default"
CORP: Cross Origin Resource Policy
As mentioned above with COEP, you can require that all subresources on your page are explicitly permitted to be loaded cross-origin with a CORP header. If you are the party responsible for serving those assets, you can define how those assets are permitted to be loaded.
Cross-Origin-Resource-Policy: (same-site|same-origin|cross-origin)
Before I explain the different values it helps to make sure we're all on the same page about what an origin is and what a site is. Take a look at the following URL:
https://scotthelme.co.uk:443/blog
The site would be eTLD+1, which is scotthelme.co.uk
and the origin is the combination of the scheme, hostname and port, which is https://scotthelme.co.uk:443
. The default port for HTTPS is of course 443 and for HTTP it's port 80 but as we're not used to seeing those due to them being the defaults, I will leave them out of these examples for simplicity. Take the following 2 URLs:
https://www.scotthelme.co.uk/
https://blog.scotthelme.co.uk/
These two URLs are same-site (both scotthelme.co.uk
) but they are cross-origin (https://www.scotthelme.co.uk/
vs. https://blog.scotthelme.co.uk/
). Now we understand the difference between a 'site' and an 'origin' we can explore the different options available to us in CORP.
The same-origin
value is the most strict and possibly difficult to deploy depending on your scenario. Take an image tag like <img src="https://img.scotthelme.co.uk/kitten.jpg">
, if it had a CORP header with same-origin
then https://scotthelme.co.uk/some-blog
would not be able to load that image as https://img.scotthelme.co.uk
and https://scotthelme.co.uk
are not the same origin.
The same-site
value it a little more relaxed and would fix the issue mentioned above too. Using the same example of
as our image and <img src="https://img.scotthelme.co.uk/kitten.jpg">
as the page loading the asset, a CORP header with https://scotthelme.co.uk/some-blog
same-site
would allow this to load as the 'site' (eTLD+1) of scotthelme.co.uk
matches for both of those URLs.
The final value of cross-origin
should not be used if it can be avoided but there are circumstances, like a public JavaScript CDN, where it is the required value. As a site intended to host and server assets for others it's clear why this value is required. Large CDN providers like MaxCDN, jsdelivr and soon cdnjs too!
CORS: Cross Origin Resource Sharing
CORS is by no means a new feature and during the explanation it sounds very similar to CORP, but it is slightly different. It allows a server hosting assets to indicate to the browser loading them what the permitted origins are for loading those assets.
Let's use an example image tag like <img src="https://img-cdn.com/kitten.jpg">
to talk through this. If you include a cross-origin image on your page like this it will result in a 'simple' request according to CORS and no special activity will take place, the image will simply load. If you do something that isn't classed as simple, like adding request headers or using a HTTP verb other than GET/HEAD/POST), then the request will be 'preflighted`. A 'preflight' is when the browser sends a HTTP OPTIONS request before sending the actual request to see if it is allowed to send the actual request. We're going to land somewhere in the middle of these two extremes but Mozilla has a great article on CORS if you want to dig into it deeper.
What we are going to do with our image tag is make one small change so that it looks like this <img src="https://img-cdn.com/kitten.jpg" crossorigin>
instead. The crossorigin
attribute tells the browser that we know this request is cross-origin and to request the asset in CORS mode. The crossorigin
attribute has two possible values but by not specifying one it defaults to crossorigin="anonymous"
or can be manually set to crossorigin="use-credentials"
. When set or defaulting to anonymous
, the browser will not send any credentials to the third-party origin like cookies, client certs or HTTP auth, it's an anonymous request as the name implies. You may recognise the crossorigin=anonymous
setting if you've used Subresource Integrity to protect yourself from scripts loaded from a third-party origin as the SRI check requires the assets to be loaded without credentials to protect against information leakage attacks.
By requesting the asset in CORS mode the browser will now require a permissive Access-Control-Allow-Origin
header on the response and if such a header is set, the asset will not be blocked by COEP. As I said above in the COEP section:
If you specify the require-corp value then a page can only load resources from the same origin or cross-origin if the other origin allows it via either:
- CORS - permission granted via a permissive ACAO header.
- CORP - permission granted via a permissive CORP policy
CORB - Cross Origin Read Blocking
All of the above mechanisms are geared up towards isolating one origin from another at the process level, but even if we fully achieved that, it is still perfectly reasonable for one origin to then request cross-origin resources from another. The blog you're reading right now pulls in JS, CSS and images from other origins and that's expected, even desired, and they end up in the same process. Given an attack like Spectre, let's now consider something like the following image tag on this page.
<img src="https://api.bank.com/balance.json">
Your browser would request that "image" and upon receiving a response there could be sensitive information inside the response depending on your authenticated state with api.bank.com
. Now, of course, upon receiving this response it's going to be JSON (as you'd expect) and that can't be rendered as an image, but the content of the response is now in the same process. Using Spectre it becomes possible for me to now possibly read that content, the content of the JSON response. This is what CORB is designed to protect against.
Part of the attack that's happening here is the hostile page requesting a Data Resource (like HTML/XML/JSON) but making it look like it's requesting a Media Resource (like an image/JS/XSS). Typically you will need to use CORS to request a Data Resource cross-origin, but this image tag trick allows the request to made without CORS mode. To get the protection of CORB, 3 criteria need to be met:
- The asset being requested is a Data Resource.
- The asset needs to declare the
X-Content-Type-Options: nosniff
header. - The asset must not allow the request via CORS.
Now, if we take a look at the same hostile image tag that we mentioned above we can talk through these requirements for it.
<img src="https://api.bank.com/balance.json">
The bank serving the response should have the MIME type correctly declared as application/json
so the browser knows that this is a Data Resource. The bank serving the response should also set the X-Content-Type-Options: nosniff
header so the browser knows not to MIME-sniff the content-type away from the declared content-type. The final thing the bank must do is not have a lax CORS policy in place. If an ACAO header is set on the response it must not include the hostile origin, which in this example is my website, so an Access-Control-Allow-Origin: *
header would be very dangerous here. The bank may have something like Access-Control-Allow-Origin: some-partner-site.com
which would be fine as it excludes my origin, the origin conducting the attack. If those criteria are met, which they are, then the protections of CORB will be applied. To be clear, this does not stop the request being made, the request will still be executed in the background. The protection that CORB offers is that the response will not be delivered to the renderer process so my origin cannot attack the response from the bank with an attack like Spectre, ultimately protecting the data.
I've talked about the X-Content-Type-Options header in the past and the Security Headers scanner also advises that websites set the XCTO header too. Hopefully this shouldn't be too difficult and the header has been around for a very long time.
Regular readers may have also identified other potential protections here and one of them is SameSite Cookies. I've spoken about SameSite a few times and it adds another layer of protection here because if the bank in our example above has a SameSite cookie then the request triggered by the image tag would not be authenticated. This means the response is unlikely to contain anything of interest to an attacker in the first place. Another great cookie protection, which is covered in Tough Cookies, is the HTTPOnly
flag. This flag prevents the cookie being read by client-side script and as a result will prevent the content of the cookie entering the rendered process, meaning it can't be attacked with an attack like Spectre.
What do I actually need to do?..
That's a heap of information and a lot more than I wanted or intended! My goal was to try and keep this blog post as short as possible to get across just the essentials, so let me see if I can boil this down to a simple task list:
- Deploy a COEP header on your site to make sure that all subresources you load are properly allowed via CORS or CORP.
- Deploy a COOP header on your site to ensure that origin is protected in its own process.
- Deploy a CORP header on any assets that you serve to ensure they are only loaded in permitted origins/sites.
- Enable CORS on all assets that you load from third parties and ensure they set an appropriate ACAO header on responses.
- Deploy an appropriate ACAO header where required on any assets/resources/responses that you serve to ensure they are only loaded in permitted origins.
- Ensure that all responses you serve have the correct Content-Type declared.
- Ensure that all responses have an XCTO header set to nosniff.
- CORB protections will be enabled when 5, 6 and 7 are completed.
- Enable the SameSite flag on sensitive cookies like authentication and CSRF.
- Enable the HTTPOnly flag on sensitive cookies like authentication and CSRF.
Support in Security Headers
At the time of writing I'm currently adding support for COEP, COOP and CORP into Security Headers so we can start to let everybody know that these new headers exist. For now, and at least the short term, they will not impact the grade achieved on the site. As with previous headers they will be shown as "Upcoming Headers" for a period of time whilst we gauge usage and adoption of these headers.
If we feel that these headers become important enough and widely deployed enough in a short space of time we may reconsider, but for now, don't expect a grade impact for not using these headers well into 2021.
Support In Report URI
As both COOP and COEP have support for reporting via the Reporting API, I'm also working on adding that to Report URI at present. Given the slightly more complicated nature of handling the variations of reports we might receive, that could take a little longer but it will be coming to an account near you!
Depending when we add support, we may or may not have COOP reports by default in Chrome just yet. If you want to enable your policy in Report Only mode and enable sending reports, you can do so in Chrome Flags:
chrome://flags/#cross-origin-opener-policy-reporting
chrome://flags/#cross-origin-opener-policy-access-reporting
This will of course be enabled by default at some point but for testing it can be good to get a head start so give it a try and see how those reports look!