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

Proposal: deprecate window.postMessage(message, '*') for use with extensions #78

Open
minfrin opened this issue Sep 8, 2021 · 22 comments
Labels
discussion Needs further discussion documentation Improvements or additions to documentation proposal Proposal for a change or new feature

Comments

@minfrin
Copy link

minfrin commented Sep 8, 2021

Right now, the only cross platform way for a content script and a webpage to communicate with each other is using the window.postMessage(message, '*') function.

This broadcasts a message to anyone who wants to listen.

https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

The problem this creates is that web extension developers and webpage developers are being invited to broadcast potentially private information to potentially hostile code.

It is proposed that any script that that is not in a position to identify the origin of that script, for example content scripts, be excluded from receiving messages via window.postMessage().

This proposed deprecation presupposes that an alternative mechanism be in place for this functionality.

Examples where security issues have been highlighted:

https://duo.com/labs/tech-notes/message-passing-and-security-considerations-in-chrome-extensions

Origin and source validation are a necessity, but even still, messages could come from JavaScript that was inserted into a page via XSS, via another malicious Chrome extension, etc. Place as little trust in messages from the DOM as possible.

https://owasp.org/www-chapter-london/assets/slides/OWASPLondon_PostMessage_Security_in_Chrome_Extensions.pdf

https://insight.claranet.co.uk/technical-blogs/hunting-postmessage-vulnerabilities

https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

Web or content scripts can use window.postMessage with a targetOrigin of "*" to broadcast to every listener, but this is discouraged, since an extension cannot be certain the origin of such messages, and other listeners (including those you do not control) can listen in.

@Jack-Works
Copy link

the only cross platform way for a content script and a webpage to communicate with each other is using the window.postMessage(message, '*') function.

That is not true. We're using document.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: "data here" })). This will not receive any cross-origin messages (like what postMessage did).

@minfrin
Copy link
Author

minfrin commented Sep 8, 2021

That is not true. We're using document.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: "data here" })). This will not receive any cross-origin messages (like what postMessage did).

I'm not following - if a webpage tried to call document.dispatchEvent() above in order to reach a content script in a webextension, what would the value of "document" be?

@Jack-Works
Copy link

I'm not following - if a webpage tried to call document.dispatchEvent() above in order to reach a content script in a webextension, what would the value of "document" be?

They're sharing the same DOM environment, so events can pass by. You can try it by yourself.

@xeenon xeenon changed the title Proposal: deprecate window.postMessage(message, '*') for use with extensions Proposal: deprecate window.postMessage(message, '*') for use with extensions Sep 9, 2021
@xeenon xeenon added discussion Needs further discussion documentation Improvements or additions to documentation labels Sep 9, 2021
@cuylerstuwe
Copy link

Being able to think of one specific scenario where something can be misused isn't sufficient to justify removing that piece from the platform.

@jcblw
Copy link

jcblw commented Sep 10, 2021

We would need an alternative to this, right now this is the only way to communicate between a website and extension in some browsers. For instance, in Safari sending document events works, but you are not able to access the detail in that document event. If something like #22 is accepted and landed in major browsers I could see the deprecation of this.

@minfrin
Copy link
Author

minfrin commented Sep 11, 2021

They're sharing the same DOM environment, so events can pass by. You can try it by yourself.

Tried document.dispatchEvent() and it works. This still appears to see the message being sent to all extensions that match the page, so the system is as secure as the weakest extension.

With document.dispatchEvent() being a thing, it looks like a safer mechanism to use.

@minfrin
Copy link
Author

minfrin commented Sep 11, 2021

We would need an alternative to this, right now this is the only way to communicate between a website and extension in some browsers. For instance, in Safari sending document events works, but you are not able to access the detail in that document event.

Can you clarify what "not able to access the detail in that document event" means? I see a field called detail in the event, and on Safari I am able to populate detail and see the results on the other side.

@minfrin
Copy link
Author

minfrin commented Sep 11, 2021

Being able to think of one specific scenario where something can be misused isn't sufficient to justify removing that piece from the platform.

Where functionality is dangerous, or has a history of being misused (as in this case), it forms a strong argument to fix or replace. Something being useful does not justify the continued existence of a security hole.

@jcblw
Copy link

jcblw commented Sep 11, 2021

Can you clarify what "not able to access the detail in that document event" means? I see a field called detail in the event, and on Safari I am able to populate detail and see the results on the other side.

Sorry misspoke, this is actually on Firefox, not Safari. The issue was not that the details of the document event were not present. The issue is that a permission error when trying to access the details.

Here is the Firefox Bug,
https://bugzilla.mozilla.org/show_bug.cgi?id=1294935

Seems like there is a workaround to get this work, but in the thread, it is actually recommended to just use postMessage.

@cuylerstuwe
Copy link

cuylerstuwe commented Sep 11, 2021

Being able to think of one specific scenario where something can be misused isn't sufficient to justify removing that piece from the platform.

Where functionality is dangerous, or has a history of being misused (as in this case), it forms a strong argument to fix or replace. Something being useful does not justify the continued existence of a security hole.

In a manifest version that already has most professional extension developers panicking to try to find workarounds for communication holes and missing features, you're proposing *a fundamental break in how content scripts work:

It is proposed that any script that that is not in a position to identify the origin of that script, for example content scripts, be excluded from receiving messages via window.postMessage().

The simple fact of the matter is that extensions are not the only things sending all-domain messages; It's a widely-used technique.

A site might send all-domain messages amongst its various child iframes/popups, with reasonable expectation that it controls those. Furthermore, an extension might have legitimate reason to both mock and listen in to those all-domain messages as a main feature of its core functionality in extending one of these sites.

Therefore: The feature you're proposing removing isn't used solely for the thing you seem to think it is.

What you're proposing here is akin to proposing e.g. the CORS * wildcard shouldn't be respected by browsers because people sometimes misuse it.

Again: It doesn't make sense to eliminate a core feature simply on the basis that people can misuse it. Access to the page's full interchangeable DOM API is a core feature of content scripts. It doesn't make any sense to tamper with this arbitrarily.

How would I write a developer tools extension whose entire purpose is to help developers debug or find insecurities by logging/mocking messages like these? Extensions are fundamentally more priveliged than websites, and in order to enable the development of devtools, they absolutely must be. Proposals for arbitrary limitations impose significantly upon scenarios like these, and regress the platform.

What's next? Taking out URL query parameters because some novice developers might not know any better than putting user credentials there? The only foolproof system guaranteed to be secure is a broken one that can't do anything useful whatsoever; That's the direction this proposal advocates for. It's extreme.

As long as postMessage communication with a wildcard is allowed on the web, it should be allowed to the same degree within browser extensions. Perhaps the action that should be taken here for the time being is a nice bold warning in extension documentation / guides: "Don't use this for X unless you know what you're doing."

I'm totally in support of a better way to communicate between extension context and page context. If a good API for that existed, then you wouldn't really need to deprecate anything; People would gradually just start using the better special-purpose API because, well... it's better. 🙂 That leaves us with the baseline postMessage tools and behavior we expect to be able to use as web developers, so that we can use our own best judgment on a case-by-case basis w/ regard to the appropriate tool to use for the problem.

@Jack-Works
Copy link

Sorry misspoke, this is actually on Firefox, not Safari. The issue was not that the details of the document event were not present. The issue is that a permission error when trying to access the details.

Yes, because content scripts in Firefox follow a different security system. To make a normal website that can access the content, you need to explicitly declare you allowing the access.

Some search keywords for this: XRay vision exportFunction XPCNativeWrapper.unwrap cloneInto. With these Firefox-only magics, you can even expose objects to the normal webpage from the content script.

@Rob--W
Copy link
Member

Rob--W commented Sep 13, 2021

The problem this creates is that web extension developers and webpage developers are being invited to broadcast potentially private information to potentially hostile code.

Extensions are more privileged than web pages. Web pages cannot expect full isolation from extensions.

Extensions should already validate any input/events/data from the web page. When they use the "message" event, then they should validate the source of the event. This is not unique to extensions, web pages that listen to messages from postMessage should also validate the potentially (cross-origin) message.

There are already plenty of ways for extensions to communicate between a content script and the document/page it's running in. I don't believe that there is a need for a new API, or even to deprecate window.postMessage.

For cross-frame communication, discussion can happen at #77.

@therealglazou
Copy link

the only cross platform way for a content script and a webpage to communicate with each other is using the window.postMessage(message, '*') function.

That is not true. We're using document.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: "data here" })). This will not receive any cross-origin messages (like what postMessage did).

This does not work cross-origin.

@ghostwords
Copy link

ghostwords commented Sep 16, 2021

There are already plenty of ways for extensions to communicate between a content script and the document/page it's running in. I don't believe that there is a need for a new API[...]

@Rob--W How should an extension send messages from a page script it injects as replacement for some third-party widget script, perhaps a video player loader (more context), and either that extension's content script (in the same page/frame) or the extension's background page?

The message sender in this case doesn't get injected before anything else on the page loads, so the extension cannot guarantee document.dispatchEvent or window.postMessage haven't been tampered with.

@Rob--W
Copy link
Member

Rob--W commented Sep 16, 2021

the only cross platform way for a content script and a webpage to communicate with each other is using the window.postMessage(message, '*') function.

That is not true. We're using document.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: "data here" })). This will not receive any cross-origin messages (like what postMessage did).

This does not work cross-origin.

This doesn't work cross-origin, but that is the subject of #77. In this issue I'd like to focus on the same-origin case.

There are already plenty of ways for extensions to communicate between a content script and the document/page it's running in. I don't believe that there is a need for a new API[...]

@Rob--W How should an extension send messages from a page script it injects as replacement for some third-party widget script, perhaps a video player loader (more context), and either that extension's content script (in the same page/frame) or the extension's background page?

The message sender in this case doesn't get injected before anything else on the page loads, so the extension cannot guarantee document.dispatchEvent or window.postMessage haven't been tampered with.

Once the page has had a chance to execute code, the content script does generally not have a way to run code in the page's context that cannot be tampered with. A way to offer a secret at first is if the content script is able to execute a script that immediately receives a reference to an object with the API (without prototype) in a closure. But it is very difficult for an extension to keep that message channel a secret.

Chromium-based browsers do presently not have a mechanism to construct a function and pass it a value. If there is a way to construct a function and pass parameters (possibly in https://crbug.com/1207006 ?), then that could be used to achieve the requested functionality here.

Firefox enables content script to get the unspoofed value of the runtime via a Firefox-specific concept called Xray vision. That could be used to create a function in the page's scope (if not blocked by CSP) and then call that function with values that are structurally cloned from the content script scope into the page's scope (via cloneInto). #75 also mentions cloneInto, but I don't expect them to be standardized.

@ghostwords
Copy link

ghostwords commented Sep 16, 2021

Since there doesn't seem to be a way to securely communicate between injected-after-page-start page scripts and content scripts as described in #57 (comment), and since this is a valid use case for several 1M user+ privacy extensions, I disagree that there is no need for a new API, and furthermore see no reason why this shouldn't be addressed at some point in a cross-browser manner.

Extension scripts being blocked by page CSPs/subresource integrity checks is its own can of worms, which we should also deal with at some point.

@dylanb
Copy link

dylanb commented Sep 22, 2021

We currently use window.postMessage with * to coordinate the sending of secure messages between frames using the background script as the relay so that no information is actually conveyed through the insecure channel.

I am simplifying here but think of it as I send a message to the background which it stores with a key I provide and then I send the target frame the key over the insecure channel (but because only it can talk to the background, I can be assured that a malicious actor cannot retrieve the package)

Removing this functionality would actually make it more difficult for us to achieve such hierarchical secure messaging unless a new API is created that allows secure messaging to any child or parent window context regardless of the origin.

Why do we need this hierarchical communcation? Because our analysis results need to know the location of the results from the child frames within the parent frame.

@minfrin
Copy link
Author

minfrin commented Sep 22, 2021

Extensions are more privileged than web pages. Web pages cannot expect full isolation from extensions.

Can you fill in more details why web pages cannot expect full isolation from extensions?

The native messaging browser extensions are split out into separate processes, with a significant amount of effort to keep code out of the browser and away from each other. On the surface this is great, until you learn the only way to communicate with the native messaging browser extension is to talk to all content scripts installed by all extensions. This is equally as insecure as the old plugins were.

For example, see the "LanSweeper Shell Execute" example in the presentation below shows the core problem. The extension leaves your system completely open to abuse.

https://owasp.org/www-chapter-london/assets/slides/OWASPLondon_PostMessage_Security_in_Chrome_Extensions.pdf

Extensions should already validate any input/events/data from the web page

Validation isn't a problem at all - the error "message 'social security number XXXX' is not understood" is not a validation problem, it's a "what is an extension that has no business hearing the social security number doing with the social security number" problem.

At the core, my communication with an extension is as secure as the weakest password of the source of the weakest extension I have installed.

The content script should be optional, not mandatory.

@Rob--W
Copy link
Member

Rob--W commented Oct 1, 2021

Extensions are more privileged than web pages. Web pages cannot expect full isolation from extensions.

Can you fill in more details why web pages cannot expect full isolation from extensions?

See #76 (comment)

It feels like the same conversation is happening in two different issues. The questions that you have raised have nothing to do with the original report here. Please continue in the other issue if you want to add more.

The native messaging browser extensions are split out into separate processes, with a significant amount of effort to keep code out of the browser and away from each other. On the surface this is great, until you learn the only way to communicate with the native messaging browser extension is to talk to all content scripts installed by all extensions. This is equally as insecure as the old plugins were.

Only privileged extension code can talk with the native app. A content script can only talk with the native messaging app if allowed to do so by the privileged extension code. Extensions can see the URL of messages from content scripts, which can already be used to limit access to the content script. Plugins were open to all web pages by default, whereas extensions can choose where and how to expose the extension's functionality (potentially implemented in a native app).

At the core, my communication with an extension is as secure as the weakest password of the source of the weakest extension I have installed.

That is true.

The content script should be optional, not mandatory.

Optional for what? For communication between a web page and an extension/native app? I've already shown that with and without a content script, the level of "security" from the web page's perspective is similar. Furthermore, there is no difference for an extension whether the message was received via a content script or via a new API.

@minfrin
Copy link
Author

minfrin commented Oct 2, 2021

Optional for what? For communication between a web page and an extension/native app? I've already shown that with and without a content script, the level of "security" from the web page's perspective is similar.

Alas I don't see where you've shown this. Can you explain this in more detail?

@carlosjeurissen
Copy link
Contributor

In my own experience being able to use postMessage to communicate is sometimes a requirement when trying to manipulate iframes. Yet often the wildmark '*' is not required as you can target the extensionOrigin like this:

let extensionOrigin = chrome.runtime.getURL('PATH/').replace('/PATH/', '');

However, even when there is a clear origin specified this API can easily be dangerous and open up security holes. Thus having an API like proposed here: #77 would be very welcome.

If / once this is implemented, to keep extensions powerful, removing window.postMessage completely or disallow '*' is not welcome. However, the browser could raise warnings when extensions use window.postMessage with the suggestion to use the frame messaging API as specified here #77.

@xeenon xeenon added the proposal Proposal for a change or new feature label Aug 31, 2022
@tophf
Copy link

tophf commented Sep 12, 2022

FWIW, there's a simple method of secure communication shown here. TLDR it creates the iframe with a random token in the URL inside a closed ShadowDOM so the site cannot see it, then sends MessageChannel's port into the iframe that will be accepted only if the message equals that random token, then this port will be used for all communication. Of course this is also slightly convoluted, hence it won't be used by 99% of extensions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Needs further discussion documentation Improvements or additions to documentation proposal Proposal for a change or new feature
Projects
None yet
Development

No branches or pull requests