Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is Sec-Fetch-Dest necessary? #16

Closed
annevk opened this issue Mar 8, 2019 · 21 comments
Closed

Is Sec-Fetch-Dest necessary? #16

annevk opened this issue Mar 8, 2019 · 21 comments

Comments

@annevk
Copy link
Member

annevk commented Mar 8, 2019

Did you consider splitting the mode rather than the destination? Perhaps that would allow for not exposing the destination at all, decreasing the amount of information we share?

@arturjanc
Copy link
Contributor

arturjanc commented Mar 8, 2019

Do you have specific concerns about exposing the destination field? The server can already infer the destination of the request based on the requested URL and the server's knowledge of the Content-Type it will emit; e.g. if the URL would end up returning an image/png response, then under normal conditions the destination has to be image because otherwise the response would be misintepreted by the requesting document. (OTOH there is value in reporting the actual destination in cases where the requester is deliberately loading the resource in an unexpected context in order to leak information.)

Also, destination doesn't tell the server anything about the user and their browser configuration, but just about the context in which the requesting document will use the response, which doesn't seem particularly useful as a fingerprinting technique.

A fairly significant benefit of exposing the destination is to allow the server to prevent attacks based on loading resources in an unexpected context: for example, GitHub folks liked the idea of rejecting any requests with destination=object to prevent leaking information by interpreting server responses as plugin resources. Similarly, it lets servers perform sanity checks to make sure that their responses don't end up being loaded in an unexpected context, e.g. ensure user file uploads aren't treated as a worklet/serviceworker/xslt stylesheet, or implement more granular protections such as locking down only cross-origin script-like requests to protect from XSSI, but not restricting other requests.

@mikewest
Copy link
Member

mikewest commented Mar 9, 2019

@arturjanc's thoughts (unsurprisingly!) match my own. destination seems independently valuable.

That said, I'd be fine moving the nested- prefix to mode if that's useful. Do we need that distinction for anything in the Cross-Origin proposal, for instance?

@annevk
Copy link
Member Author

annevk commented Mar 9, 2019

There's quite a few destinations that make sense for images. And even if you block destination=object, would that block an <object> with a browsing context that's navigated to a plugin? (Presumably destination is nested-document in that case, albeit not clearly defined.)

(Cross-Origin-Opener-Policy only takes effect for non-nested navigations, but that wasn't really why I raised this. We need to handle that header in navigation, not fetch.)

@arturjanc
Copy link
Contributor

There's quite a few destinations that make sense for images.

I looked at the data for requests to our applications from clients with the Chrome flag enabled, and I see that 99.87% requests which return an image/* MIME type have a destination of image or empty (which is the case for Fetch / XHR, <a ping>, etc). These loads are already distinguishable by looking at request headers (e.g. the presence of Origin), and will be explicitly differentiated by exposing the mode.

The handful of remaining cases include direct navigations to images (also visible via mode), loading image/svg+xml resources via <object>, and obviously mismatched Content-Types, e.g. tracking requests where the response is ignored by the client. I don't think that in any of the cases above the server learns anything new by knowing the destination other than confirming its assumption about the context in which the response will be used.

More generally, I'd posit that in benign scenarios the server must already have enough information in the request to return the appropriate type of resource for the context -- this information is usually implicit in the URL. I'm having some trouble seeing how making the destination explicit (which is the valuable part for making security decisions) would tell the server anything it couldn't already infer.

And even if you block destination=object, would that block an <object> with a browsing context that's navigated to a plugin?

The concern is about attacks similar to Rosetta Flash where a resource with a benign Content-Type can be forced to be interpreted as a plugin when loaded via <object>. This can happen e.g. for JSONP responses or text files uploaded by the user.

Such responses would not be treated as plugin resources when navigated to directly, but would be recognized as plugins (and given same-origin access to the hosting origin) when the attacker's site loads them via <object>/<embed>. So blocking destination=object should be sufficient to prevent this; of course the server could additionally restrict direct navigations to such resources by checking the mode.

Note that this is just one example. There have been other attacks based on intentional Content-Type mismatches, e.g. exfiltrating data from HTML by loading it as CSS; it seems useful for a server to make sure that its responses won't be used in a wrong context, especially for cross-origin requests.

@annevk
Copy link
Member Author

annevk commented Mar 11, 2019

If you can already determine it, there's even less reason to add it to the request.

Note that you'd also need to block destination=embed, but it seems that a better defense there would be using/exposing "sandboxed plugins browsing context flag" somehow. It's probably a safe assumption no new contexts are able to load plugins, but it doesn't seem technically sound to advocate blocking embed/object over using that.

This is the same as with loading HTML as CSS. X-Content-Type-Options and in general browsers being stricter on CSS fetches are what you want there. See whatwg/fetch#721 for closing down the last couple non-CORS holes.

@arturjanc
Copy link
Contributor

If you can already determine it, there's even less reason to add it to the request.

You can determine it for legitimate traffic -- if my application loads a resource as an image and the server replies with anything other than image-like bytes, the load will fail. Developers don't load image responses as script, style or other destinations because such loads have no meaning; so for non-malicious traffic the Content-Type emitted by the server is a very close match to the destination of the request (modulo the CORS aspect mentioned above). The server doesn't learn anything substantially new from destination that it couldn't otherwise infer from the Content-Type it emits.

You cannot determine it for malicious traffic where a resource is loaded as a mismatched type on purpose by the attacker. For no-cors requests in particular, it's valuable for a server to reject requests where the destination doesn't match the server's expectation of how its response will be used. For example, if my forum allows users to upload their avatar images, I don't want them to be loaded cross-origin as an object, serviceworker, AppCache manifest, or anything that's not an image. I'm super happy to talk about specific attacks we've seen, but I'd posit that there have been enough of them over the years that we probably don't need to go into details on this bug ;)

Similarly, it's valuable for a server to globally lock down the application and allow only cross-origin requests for a subset of resource types that the application wants to expose externally. If I expect stylesheets to be the only type of resource in my application that can be loaded cross-origin, I can create an a priori allowlist that will ensure my responses can't be used in any other context -- this is particularly useful as a server-side complement to CORB/CORP.

Taking a step back, it would be very helpful if you could elaborate on your specific concerns about destination -- I feel like I don't have a good handle on the problems you're worried about which makes it difficult to give good answers beyond what I've tried to capture above.

@annevk
Copy link
Member Author

annevk commented Mar 11, 2019

Concerns I have:

  • Request bloat
  • Servers using this as a content negotiation strategy
  • Exposing more contextual information than needed

@arturjanc
Copy link
Contributor

Thanks for the list, @annevk!

  • Request bloat

This is certainly a concern, sending more information will necessarily increase the size of requests. In the TAG review @mikewest mentioned possible approaches to reduce the size, but they are somewhat at odds with readability and ease of adoption in that they require server-side parsing code, which developers prefer to avoid.

IMHO sending site, mode and destination as currently proposed and finding a more compact way to express boolean flags (e.g. Sec-Fetch-User or sandboxing) may be a better way to avoid bloat without reducing the mechanism's security value. That said, I don't think anyone is strongly opposed to iterating on the syntax so we can definitely change things if we find more reasonable solutions.

  • Servers using this as a content negotiation strategy

destination doesn't exactly seem granular enough to be useful for content negotiation because the server only receives the resource type without getting any more information about what formats the client supports. E.g. if the server knows the client requested an image it knows that if it replies with a non-image response the request will likely fail, but that's not sufficient for it to respond with a particular type of image.

  • Exposing more contextual information than needed

It's likely that many servers won't use this information so it may be more than what's needed for them; this is arguably similar to Accept-* and User-Agent headers which are also frequently ignored, but in some cases are crucial for developers.

I tried to identify scenarios where destination could provide the server with interesting / sensitive information, and I'm still fairly hard-pressed to find a situation where this would be a concern:

  • It doesn't provide the server any new information about the user herself -- all the destination values are a function of how the requesting document loads resources, not of the user's browser/configuration (other than demonstrating that the user has a new enough browser to send Sec-Fetch-* headers)
  • It doesn't provide the server with interesting new information about how its resource is requested by a non-cooperating client beyond exposing a coarse-grained value that in practice is almost always less granular than the information carried in the response's Content-Type.
  • If we assume a cooperating client, the request's destination is already exposed via the Request object.

I also feel strongly about avoiding exposing sensitive new information, but if we're considering reducing the capabilities of a fairly critical security mechanism (destination has several important use cases), we should likely have specific examples of the problems that we're worried about.

@mikewest
Copy link
Member

Let's split this into two issues:

Should the nestedness (or poppedupness, as per #17) of a given navigation be represented in the destination field, or in the mode field?

I don't have a strong opinion here, as either field seems to be equally deployable and would give servers the information they need to make reasonable decisions.

I'll defer to you, @annevk. If you'd prefer to represent these navigations in Fetch/HTML as a nested-navigation mode rather than as a nested-document destination, I'm totally fine with that. What would you like us to do here?

If we move nestedness elsewhere, is destination valuable enough to send?

I don't think I agree with the implied claim above that the information in destination is already inferrable from other request headers. At least in Chrome, we don't distinguish between most request types in the Accept header, but only between navigations, images, CSS, and everything else. Firefox is similar, though it doesn't distinguish images, and does distinguish fonts.

This leaves more than a few types out there that are useful to distinguish. For example:

  • XSSI attacks rely on loading content via <script> or new Worker() cross-origin. If we tell the server that a given request is being interpreted as a script-like destination, we give them the opportunity to make a better decision about what to deliver as a response.

  • As noted above, object/embed seems like a good thing to distinguish, given the different capabilities/embedding origin model of plugins. I don't actually understand the suggestion you made above, @annevk: it's certainly possible for pages to prevent themselves from loading plugins today (via object-src 'none'), but it doesn't yet seem possible for servers to prevent their (potentially untrusted/user-uploaded) resources from being loaded as plugins elsewhere. That's still a real danger, given plugins' origin model.

  • Even for the distinguishable types above, it seems friendlier to developers to deliver a standardized value rather than parse a browser-specific Accept header. Chrome sends text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 for navigations, for instance, while Firefox sends text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0. Close, but not quite the same!

  • Less importantly, this would give us the eventual ability to deprecate the Service-Worker: script header, as Sec-Fetch-Destination: serviceworker provides the same ability.

I suspect folks like @arturjanc will discover more combinations of fetch characteristics that can be distinguished and mitigated as they experiment more with the feature on actual applications in the wild.

That is to say, I see the "content negotiation strategy" bits of this as a feature, not a bug, especially insofar as the negotiation is binary in nature (e.g. 200 with content, or a 403 with none).

Perhaps you could elaborate on the risks you see there?

@annevk
Copy link
Member Author

annevk commented Mar 12, 2019

The server cannot reliably tell the destination from this header due to service workers. It isn't bound to the response in some manner so therefore can be wrong. It'll be hard to describe specific mitigations that apply generally.

If the plugin thing is a real problem, let's make X-Content-Type-Options work there.

I also don't think my general argument is (or has been) that the information is available otherwise. It's that it's not clear to me this needs to be exposed and provides the correct security guarantees a server might be looking for.

@mikewest
Copy link
Member

Service workers can't set Sec- headers, can they?

Or, do you mean that the service worker can launder any header type into "" by refetching? That's interesting and I hadn't thought of it!

@annevk
Copy link
Member Author

annevk commented Mar 12, 2019

Right, that they can create their own request using fetch() and feed the responses from that to whatever context they desire.

@mikewest
Copy link
Member

That's exciting. @arturjanc, WDYT?

If the plugin thing is a real problem, let's make X-Content-Type-Options work there.

It's a problem folks at GitHub (like @ptoomey3) have asked about specifically. I can imagine XCTO helping (though now I'm wondering what our existing model is for plugin content and laundering through service workers).

@arturjanc
Copy link
Contributor

To better understand the Service Worker issue: does it allow spoofing any destination value, i.e. can an attacker feed a response for a request with destination=image to a <script> (for example)? Or does it rely on a fetch() from the SW so that the attacker's request will always have destination=empty?

If it's the former, then I agree that it would largely undermine the security value of providing destination because the server couldn't rely on it to make security decisions. However, if it's the latter, then the server would just need to take into account that destination=empty requests could end up being fed into any attacker-controlled context and potentially reject them.

As another example, consider the Spectre prevention role of Sec-Fetch-* headers. Let's say that an application has authenticated images that it wants to protect from being loaded by an attacker, but it has other no-cors resources (e.g. scripts or stylesheets) that are legitimately loaded cross-site. The server could allow cross-site traffic only if it has a destination of script or style and rely on the browser to not supply the responses with a mismatched Content-Type to the renderer (via X-C-T-O: nosniff). This would make it much easier to build a server-side complement to CORP; it would also facilitate the deployment of CORP itself because it provides developers with direct information about their CORP-eligible resources which are being loaded cross-site.

@annevk
Copy link
Member Author

annevk commented Mar 15, 2019

A service worker can feed destination=image request response to a <script>, assuming it gets such a request (it cannot create one itself). At least, I'm pretty sure we don't reset request's destination if the service worker doesn't touch anything from the request.

@annevk
Copy link
Member Author

annevk commented Mar 18, 2019

I'm 99% sure my above comment is incorrect. Unless a request fully bypasses a service worker (i.e., the service worker does not touch it or store it) there's no way it should be allowed to preserve destination as that would defeat CSP. (Hopefully new mechanisms that allow bypassing some of the service worker take this into account...)

@jakearchibald
Copy link

With Range headers, we allow them to pass through the service worker as long as the request is unmodified. Does that model work here?

That means the server can trust that the Sec-Metadata header fits the rest of the request data.

The service worker could use the response as a response to a different request, so destination=image wouldn't guarantee that the response would only be used as an image, but it guarantees that the request was created by an image.

I don't know if that's enough. Is there an example of an attack that Sec-Metadata prevents?

@mikewest
Copy link
Member

I think dest generally is going to need more discussion. I know @jakearchibald and @arturjanc are working through some of those questions in a separate thread. Since that's the direction this bug has taken, I'm going to rename it, and file a different bug for the nested-navigate Sec-Fetch-Mode header, which seems like a good idea regardless of the decision on Sec-Fetch-Dest.

#22 covers that work.

@Jack-Works
Copy link

👀 I'm using Sec-Fetch-Dest header in my experimental project to response files as different mineType. e.g. If Sec-Fetch-Dest equals to script and the target file is a JSON file, I will change the request to application/javascript and response with export default ${JSONContent}.

It seems there is no other convenient way to distinguish what a browser want by other information than this header so this header is necessary for me.

@Malvoz
Copy link
Contributor

Malvoz commented Nov 16, 2019

While perhaps somewhat of a niche use case - since we don't have media-types for worker and serviceworker (I think @jakearchibald used a fictive text/sw+javascript in one of his blog posts) this seems quite useful, as sometimes they need special treatment, e.g. needing to avoid file versioning, or properly setting CSP, different caching rules to regular scripts, etc.

@mikewest
Copy link
Member

Thanks for the feedback, folks. After more discussion than expected, I think we've generally come to the conclusion that Sec-Fetch-Dest is both helpful and necessary. I've removed the issue in the spec, and expect to ship it in Chromium shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants