w3c / webappsec-permissions-policy

A mechanism to selectively enable and disable browser features and APIs
https://w3c.github.io/webappsec-permissions-policy/
Other
399 stars 155 forks source link

Send reports for Permissions Policy violations in iframe to parent frame's endpoint #537

Closed shhnjk closed 1 month ago

shhnjk commented 9 months ago

From https://crbug.com/1501168:

Permissions Policy violation inside cross-origin iframes are not sent to the top frame's reporting endpoint. While this might be due to a security concern that cross-origin information would be leaked, without this information, it is really difficult to deploy Permissions Policy because sites who's delegating permissions won't know what will break (because they don't receive violation).

So far, the discussion has been leaning to send reports whenever iframe's allow attribute was specified with an origin which is blocked by the Permissions Policy.

To implement this, we will likely add Is feature enabled in document for origin to Parse policy directive.

We'd like to know if there is a concern to this change.

CC: @arturjanc, @mikewest, @ddworken, @salcho

annevk commented 9 months ago

I don't think that addresses the concern raised. And I don't think we can address the concern raised without compromising on the same-origin policy, which we shouldn't do.

shhnjk commented 9 months ago

Right, the solution proposed will send a report for each iframes which has an allow attribute with denied origin, but it does not tell whether the permission was used inside an iframe. I think this is a good compromise, as this does not leak new information to the page (i.e. this can be done through JavaScript), but it is definitely better than the status quo. So I think it's worth implementing.

Any objection to implementing this?

annevk commented 9 months ago

Yes, it's not clear to me it's solving an actual problem.

clelland commented 9 months ago

@shhnjk, is the idea here that simply constructing an iframe, which tries to delegate a permission that it is not allowed to, would trigger a report in the embedding page?

arturjanc commented 9 months ago

Yes, the idea is to report when the document delegates a permission to an iframe whose origin is not allowed to receive the permission in the Permissions Policy configuration. I think this has two main benefits:

  1. It doesn't introduce the security concern that @annevk pointed out because the violation doesn't depend on any activity in the iframe itself. The report is sent upon delegation, which is in control of the embedder and thus something the embedder already knows -- it would not expose if the iframe actually uses the delegated permission.
  2. It provides data to the embedding page that allows it to lock down the Permissions Policy configuration to only origins that the application needs to allowlist. Receiving reports about any origins which are delegated permission, but which are not allowlisted in the Permissions Policy header, would let developers make sure that the list is comprehensive and allow them to actually enforce PP restrictions.
    • In this model developers would create PP configurations based on which origins they delegate the permission to, but not which origins use the permission after delegation.

Taking a step back, I see this as a relatively small change with large security impact because this is what any medium-sized application will need to be able to enable Permissions Policy restrictions and control cross-origin permission delegation. As @shhnjk mentioned at TPAC, this is currently a significant security gap for applications which use strict CSP - an HTML injection can delegate any of the affected origin's permissions to the attacker, even if the attacker is prevented from executing script in the affected app by CSP. It seems useful for developers to be able to prevent this (which would also help them ensure their third-party widgets don't overly broadly delegate the top-level application's permissions); and getting reports for cross-origin permission delegation is pretty much necessary for developers to be able to lock down their Permissions Policies.

clelland commented 9 months ago

Thanks, @arturjanc -- that makes sense. I was having trouble imagining the use case, but that makes it much more concrete.

A couple of questions --

  1. Would you consider this a violation report like any other for use of the feature? I'm thinking that this might be something new -- an "incorrect delegation" violation, perhaps reporting for the specific feature, or perhaps for permissions policy itself.
  2. "It provides data to the embedding page that allows it to lock down the Permissions Policy configuration to only origins that the application needs to allowlist. Receiving reports about any origins which are delegated permission, but which are not allowlisted in the Permissions Policy header, would let developers make sure that the list is comprehensive and allow them to actually enforce PP restrictions." This would have to be restricted to reporting the URL in the iframe's src attribute; if the frame itself navigates away from that URL, either through an HTTP redirect or user action, or any other navigation, then we couldn't report on the URL of the new document in the frame (maybe with an exception if it is same-origin with the embedding page.) Is that still as useful?
  3. Do you see this working the same way with Permissions-Policy-Report-Only? I expect it would work similarly; that if a delegation would not be allowed by the PPRO header, we could report and allow it to proceed.
arturjanc commented 9 months ago

Would you consider this a violation report like any other for use of the feature? I'm thinking that this might be something new -- an "incorrect delegation" violation, perhaps reporting for the specific feature, or perhaps for permissions policy itself.

Ah, interesting. I see that Permissions Policy has only a single report type of permissions-policy-violation - I think you're right that it would benefit from creating a different report type for the delegation case. FWIW reporting for Cross-Origin Opener Policy is conceptually similar because COOP violations come in different flavors; to deal with this, COOP introduces a number of different report types (e.g. navigation-to-response, access-to-opener, etc). Something like this would make sense to me here as well.

This would have to be restricted to reporting the URL in the iframe's src attribute; if the frame itself navigates away from that URL, either through an HTTP redirect or user action, or any other navigation, then we couldn't report on the URL of the new document in the frame (maybe with an exception if it is same-origin with the embedding page.) Is that still as useful?

This is an excellent point. My understanding is that a cross-origin navigation in an iframe with delegated permissions will remove permission delegation unless the post-navigation origin is explicitly specified in the allow attribute (allow="fullscreen https://origin.example https://another-origin.example"). I think this means that by default this is "safe" compatibility-wise in the sense that if you use the simplified syntax you don't end up in a situation where you can delegate a permission to an origin that wouldn't be reported in a "incorrect delegation" report as we're discussing here.

To address this, @shhnjk suggested that the violation report could contain the full allowlist specified in the allow attribute. E.g. an "incorrect delegation" report in the example above could contain something similar to CSP's originalPolicy -- which for the example above would be "https://origin.example https://another-origin.example" (which developers could add to their header-based Permissions Policy allowlist).

Also, I think it's quite rare in practice for permissions to need to survive cross-origin iframe navigations. So even without this additional reporting I expect most developers would be able to safely roll out locked down Permissions Policies if we give them "incorrect delegation" reports.

Do you see this working the same way with Permissions-Policy-Report-Only? I expect it would work similarly; that if a delegation would not be allowed by the PPRO header, we could report and allow it to proceed.

Yes, I think so.

clelland commented 9 months ago

This is an excellent point. My understanding is that a cross-origin navigation in an iframe with delegated permissions will remove permission delegation unless the post-navigation origin is explicitly specified in the allow attribute (allow="fullscreen https://origin.example https://another-origin.example"). I think this means that by default this is "safe" compatibility-wise in the sense that if you use the simplified syntax you don't end up in a situation where you can delegate a permission to an origin that wouldn't be reported in a "incorrect delegation" report as we're discussing here.

That's correct, and it was designed to be safe in that way. And by default, if you just specify allow="fullscreen", that implicitly references just the origin described by the iframe's src (or srcdoc, sandbox, etc) attribute.

These can get out of sync in two ways, though. First, you could try to delegate permission to a page that redirects away:

Permissions-Policy: fullscreen=(self, "https://adnetwork.example")
<iframe src="https://adnetwork.example/" allow="fullscreen"></iframe>

If the frame does a 302 redirect to https://advertiser.example, then the permission will be blocked, but we can't report on that.

Conversely, if you did something like

Permissions-Policy: fullscreen=(self, "https://github.com")
<iframe src="https://www.github.com/" allow="fullscreen"></iframe>

Then this proposal would report that as a violation. However, when the frame redirects from www.github.com to github.com (no www.), the feature would in fact be allowed in the frame.

I think this is the best we can do, though, without revealing anything more about the actions within the frame itself.

arturjanc commented 9 months ago
Permissions-Policy: fullscreen=(self, "https://adnetwork.example")
<iframe src="https://adnetwork.example/" allow="fullscreen"></iframe>

If the frame does a 302 redirect to https://advertiser.example/, then the permission will be blocked, but we can't report on that.

I agree, but in this case the permission will be blocked not because the developer added the Permissions-Policy HTTP header, but because the delegation in the allow attribute doesn't include the post-redirect origin in the first place (right?).

If we see reporting as a way to enable developers to set restrictive Permissions-Policy headers (to only allow delegation to origins to which the application legitimately intends to delegate permissions), then not reporting in this case is fine. That's because adding the Permissions-Policy header will not break existing functionality -- the post-redirect URL has not been properly allowlisted in the iframe's allow attribute, so delegation doesn't happen anyway, even in the absence of the header. It's only if the iframe uses the extended syntax (allow="geolocation https://advertiser.example") to allow the post-redirect origin to get the permission that we would run into a situation where the additional origin restrictions set in the header could block delegation and result in breakage. This is what we could address with the originalPolicy idea from above - because in that case the violation would include the post-redirect origin (https://advertiser.example) so developers would know they need to add it to their header.

Permissions-Policy: fullscreen=(self, "https://github.com")
<iframe src="https://www.github.com/" allow="fullscreen"></iframe>

Then this proposal would report that as a violation. However, when the frame redirects from www.github.com to github.com (no www.), the feature would in fact be allowed in the frame.

This kind of false positive is fine - practically, the worst that would happen is that developers would see violations for https://www.github.com and add the origin to their policy alongside https://github.com. It's not ideal, but this seems like a rare case and even adding such a spurious origin to the Permissions-Policy allowlist would still be a significant improvement compared to not having any restrictions in place.

shhnjk commented 9 months ago

I agree with @arturjanc that the false positive would be fine.

Would you consider this a violation report like any other for use of the feature? I'm thinking that this might be something new -- an "incorrect delegation" violation, perhaps reporting for the specific feature, or perhaps for permissions policy itself.

Maybe I prefer to have additional disposition types, which are potential-enforce and potential-report with these reports.

shhnjk commented 8 months ago

@annevk, let us know your thoughts given above discussions.

annevk commented 8 months ago

I'm still not really convinced this is solving anything. This is information you by-and-large already have access to and wouldn't help with removing origins that may or may not use a permission.

However, when the frame redirects from www.github.com to github.com (no www.), the feature would in fact be allowed in the frame.

I guess that means the header can override the allow attribute to some extent?

arturjanc commented 8 months ago

I'm still not really convinced this is solving anything. This is information you by-and-large already have access to

Violation reports delivered by the Reporting API (for features such as CSP, COEP, Permissions Policy, etc.) often have information that the page has access to in principle, but which is quite difficult to collect in practice.

For example, instead of CSP violation reporting a page could load a script to look at its DOM and find all instances of inline event handlers, resource locations, etc., and then send this information to the server to check if a given CSP would be compatible with the application. However, this runs into two substantial practical problems:

  1. It requires the application to deploy custom JS on every endpoint where it wants to enforce a given policy. This is difficult because applications frequently have a large number of HTML templates, static pages, and non-template-based HTML endpoints, all of which would need to be modified to include the JS to collect the necessary data.
  2. It introduces a mismatch because enforcement of the policy (again, for CSP, COEP, PP, etc.) is done through an HTTP header, but violation data is collected by a script. Any failure to run the script on all endpoints which enable the HTTP header but aren't compatible with the given policy will break the application when the policy is enforced -- and because it's common for applications to have centralized locations where they can set an HTTP header, it's quite likely that a header would apply to more endpoints than script-based data collection would cover.

This is why HTTP header-based report-only violation reporting is really necessary in practice when enabling any kind of security policy that restricts what an application can do -- in this case, to restrict where permissions can be delegated.

and wouldn't help with removing origins that may or may not use a permission.

The case we're discussing here is an application that doesn't yet use Permissions Policy, but would like to enable it to restrict which embedded origins can request permissions (e.g. to protect users from sensitive permissions being delegated to an attacker-controlled iframe in the case of any HTML injection).

Without this reporting change it's practically extremely difficult to enable any Permissions Policy in a moderately complex application, because enforcing the policy would break all instances of cross-origin permission delegation, without notifying developers of this fact through report-only violations.

@annevk you're right that if an application already legitimately delegates an unused permission to an origin then this model would not let the developer know that the permission is unused and would result in this origin being allowlisted in the Permissions Policy. But even if this happens then an enforcing policy will just allowlist a small number of potentially unnecessary origins, but still prevent delegation to any other location, e.g. in case of an HTML injection. IMO this is a very small price to pay in order to both unblock useful violation reporting for Permissions Policy and keep this in line with the same-origin policy and avoid exposing any information about the activity of cross-origin frames.

However, when the frame redirects from www.github.com to github.com (no www.), the feature would in fact be allowed in the frame.

I guess that means the header can override the allow attribute to some extent?

@clelland knows more about this, but I don't think so. IIUC if an iframe's allow attribute specifies a list of origins to which a permission can be delegated then the header can't permit delegation to any other origins (and can't result in permission delegation to iframes without the allow attribute).

Actually, I think we got confused here because in Ian's scenario (<iframe src="https://www.github.com/" allow="fullscreen"></iframe> with a redirect of the iframe to https://github.com) the permission won't work on github.com because the post-redirect origin is not listed in the allow attribute. In this case, the header cannot re-enable the permission on its own, the allow attribute would have to include the additional origin.

clelland commented 8 months ago

However, when the frame redirects from www.github.com to github.com (no www.), the feature would in fact be allowed in the frame.

I guess that means the header can override the allow attribute to some extent?

Not really -- in that example, the header allowed only self and github.com, so even though the allow attribute said allow=fullscreen, a page at www.github.com wouldn't be allowed to use fullscreen. However, if that frame redirected, or navigated, to github.com, then suddenly the header, attribute, and loaded origin are all in alignment again, so fullscreen is allowed.

@clelland knows more about this, but I don't think so. IIUC if an iframe's allow attribute specifies a list of origins to which a permission can be delegated then the header can't permit delegation to any other origins (and can't result in permission delegation to iframes without the allow attribute).

That's true -- although I usually think about it the other way: if the header specifies a list of origins, the allow attribute can't delegate to anything not on that list. But it's an AND operation between the two, so the logic works either way.

clelland commented 8 months ago

Actually, I think we got confused here because in Ian's scenario (<iframe src="https://www.github.com/" allow="fullscreen"></iframe> with a redirect of the iframe to https://github.com) the permission won't work on github.com because the post-redirect origin is not listed in the allow attribute. In this case, the header cannot re-enable the permission on its own, the allow attribute would have to include the additional origin.

Oohhh... I got confused there, too :(

You're correct, and the way I worded it, even post-redirect, the permission shouldn't be allowed.

The scenario I meant to describe was one where the HTML looked like

<iframe src="https://www.github.com/" allow="fullscreen *"></iframe>

In this case, the header would block fullscreen if the page loaded in the frame came from www.github.com, but if it redirected to plain github.com, then the allow="fullscreen *" attribute would be permissive enough to allow fullscreen again (and the header would be in agreement).

thngkaiyuan commented 8 months ago

I'm really appreciative of the direction we're taking with this change, aiming to simplify the process for web developers to implement secure, non-breaking permission policies. One aspect I'd like to understand better is how this proposed change would affect reporting in scenarios where permissions are implicitly delegated to iframes (i.e. iframes that don't use the allow attribute to delegate permissions, but receive permissions implicitly because there's no permission policy enforced for the permission used in the frame).

My assumption is that the proposed changes wouldn't impact such implicit delegations of permission. Reporting only permissions that are implicitly delegated due to usage in such iframes could leak usage information from the framed origin. Conversely, reporting all permissions that could be implicitly delegated to such iframes could generate a large volume of reports that aren't particularly meaningful.

@shhnjk, could you confirm if my understanding is correct? Specifically, would this proposed change not impact cases where permissions are implicitly delegated without the allow attribute?

shhnjk commented 8 months ago

I'm not sure if there is an example of a permission which implicitly delegates without declaring in the allow attribute. Could you give me an example?

thngkaiyuan commented 8 months ago

I stand corrected. After further testing, I've confirmed that permissions are not implicitly delegated to frames. Thank you for the clarification!

shhnjk commented 7 months ago

@clelland, should we create new disposition types called potential-report and potential-enforce, or would you prefer new report type of potential-permissions-policy-violation?