WICG / Realms-Initialization-Control

Introduce security controls to same origin realms in web applications
MIT License
15 stars 0 forks source link

explain realm-init behavior #34

Open caridy opened 2 months ago

caridy commented 2 months ago

From the following paragraph:

As a result of the CSP directive pointing to monitor.js, the security logic introduced by it will be applied to all same origin realms that can be manipulated against the execution environment of the application (as opposed to allowing scripts to create a new realm and use its clean copy of fetch).

It is not clear if the monitor.js script will get executed in the root realm or not. From the readme, it seems that it only applies to other child realms, because otherwise you have no way to gain access to those capabilities (e.g. fetch) in the root realm, but then I see no references to the undeniables.

Here is a concrete example:

Let's assume that the new header points to remove-fetch.js to prevent child realms to access fetch global value all together. Then the attacker does the following:

function stealFetch() {
    try {
        return window.fetch;
    } catch (err) { // Uncaught Error: Access to powerful capability "fetch" is forbidden
        return window.top.fetch; // get the fetch via the undeniable top
    }
}

// function fetch() { [native code] }
const newFetchInstance = stealFetch()

Specifically, the problem I see here is that remove-fetch.js has no way to replace/tame top because it is undeniable, and since it does not run in the root realm, the fetch from top is going to have full capabilities.

Note: this is not a problem when creating new ShadowRealm instances because they don't have undeniables.

weizman commented 2 months ago

IMO the context of the proposal makes this pretty clear, but no harm in explicitly stating so - mind approving? https://github.com/WICG/Realms-Initialization-Control/pull/36

caridy commented 2 months ago

That PR makes it clear. Now, the concern is how the top realm will gain or hold some of these capabilities that are going to be denied to child realms. This sounds a lot like the same trap we felt into with CSP, where everyone must play by the same rules, which hinders the ability to adopt it because it is too hard. Hence we end up with Trusted Types to open the door for someone to have a magic wand and gain additional capabilities that are otherwise denied to the rest of the code. E.g.: I will like to deny access to the session id from cookies or something like that for child realms, but I will like to still have access to it as part of my app, how to achieve this?

weizman commented 2 months ago

Well, since this feature is designed to allow code-based customization of behaviour of capabilities, I guess JS's expressiveness comes into play here. At the most basic level, the reconfiguration of the APIs can take such differences into account:

// Content-Security-Policy: init-realm: /init.js
if (window !== top) {
  const block = () => { throw 'BLOCKED' }
  defineProp(window.document, 'cookie', { get: block } )
}

Combined with other web capabilities, my vision is that such getters will determine whether to grant access to a capability or not (and to what extent) based on whether the entity asking for it is permitted to (should script coming from src X be allowed to access capability Y? should DOM node A be allowed to walk through to DOM node B?).

While difficult, this vision is very much possible in user land. The only blocker in achieving that is the same origin concern which would be solved when this proposal lands.

caridy commented 2 months ago

Oh, no, I don't think that works because of the undeniable. The code snipped above, where cookie is only really blocked for child realms, while they have access to top.document.cookie just fine via the undeniable top accessor. That's the thing, for this to work, you only have to paths, and both are very difficult paths:

  1. you must change all code in the root to somehow gain access to capabilities that are otherwise forbidden for other realms, and doing so without exposing those via the window ref of the root (this is very hard).
  2. or you must reform the undeniables, (this is very unlikely at this point).

In essence, this is the same dilemma as trusted types, where there is a magic wand, and if you manage to get your hands on the magic wand, you can do wonders, otherwise, you are at the mercy of the capabilities allowed by CSP.

weizman commented 1 month ago

Via the undeniables, access to the reconfigured descriptors will always be possible, this proposal won't focus on that problem. However, having access to a descriptor, does not mean the descriptor would necessarily provide the capability to anyone that asks for it.

"The code snippet above" does not demonstrate that by itself, but only together with the paragraph that comes right after it about "web capabilities", which should result in something more sophisticated:

// Content-Security-Policy: init-realm: /init.js
if (window !== top) {
  const block = () => { throw 'BLOCKED' }
  defineProp(window.document, 'cookie', { get: block } )
} else {
  const mitigate = () => {
    const entity = whoIsAskingForDocumentCookie()
    if (!allowedEntitiesToAccessCookie.includes(entity)) {
      throw 'BLOCKED'
    }
    return realCookie
  }
  defineProp(window.document, 'cookie', { get: mitigate } )
}

And based on your (correct) take, this is the former of the two suggested paths - it's very hard telling who's asking for a capability in a safe way, but with some hard R&D it is possible (proxies also helped a lot in paving that way).

It's also important to mention that the RIC proposal makes sense in slightly different context as well, for example, what if we want to compose a monitoring system that only monitors consumption of powerful capabilities instead of mitigating them? For that, the same redefinition can take place for both the top and the rest of the realms, so that this problem doesn't apply anymore:

// Content-Security-Policy: init-realm: /init.js
defineProp(window.document, 'cookie', { get: () => {
  reportToServer({msg : 'cookie was accessed' })
  return realCookie
} } )

So it's really a matter of use case, but all in all, having a power such as RIC would allow builders to ship control over capabilities their app provides - a great and important accomplishment from my perspective.

caridy commented 1 month ago

I think whoIsAskingForDocumentCookie in the example above is the problem. In the web, as it stands, it is impossible to know who is calling when it comes to undeniables. My recommendation is to attempt to either remove or perhaps relax the "traversal undeniables" (maybe making top and co. configurable/non-writable) so RIC can distort their behavior as well.

weizman commented 1 month ago

In the web, as it stands, it is impossible to know who is calling when it comes to undeniables.

I don't see the problem you're referring to, if you get a chance to elaborate on this