Closed Bug 1880429 (CVE-2026-6755) Opened 2 years ago Closed 6 months ago

Firefox 122.01: postMessage event.source validation bypass

Categories

(Core :: DOM: postMessage, defect, P2)

Firefox 122
defect

Tracking

()

RESOLVED FIXED
150 Branch
Tracking Status
firefox-esr115 --- wontfix
firefox-esr140 --- wontfix
firefox148 --- wontfix
firefox149 --- wontfix
firefox150 --- fixed

People

(Reporter: frsantos9989, Assigned: edenchuang)

References

Details

(Keywords: csectype-mitigation-bypass, reporter-external, sec-moderate, Whiteboard: [fixing in 2003504][adv-main150+])

Attachments

(1 file, 1 obsolete file)

(Droping the md report here because I didn't know the original placeholder allowed md)

Firefox 122.01: postMessage event.source validation bypass

Summary

I identified a security vulnerability in Firefox 122.01 that allows sending postMessage from cross-origin sources. This bypasses the expected event.source validation, potentially leading to security risks such as cross-origin actions or data exposure.

By accessing win.location.constructor.constructor of the initial about:blank page, an attacker can execute this.postMessage in the context of the cross-origin page.

Environment

  • Browser Version: Firefox 122.01.
  • Operating System: ZorinOS 6.5.0-17-generic #17~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC

Steps to Reproduce

  1. Host the following HTML page on a server or open it directly in a browser:

    <html>
    <body>
        <script>
            win = open("https://mdn.github.io/webextensions-examples/content-script-page-script-messaging.html");
            loc = win.location.constructor.constructor;
            setTimeout(() => {
                loc('return this.postMessage({"direction":"from-content-script","message":"Message from the page"}, "*")')()
            }, 3000)
        </script>
    </body>
    </html>
    
  2. After 3 seconds, observe that a message is sent to the demo extension's page, bypassing the expected event.source validation.

Expected Behavior

The event.source property value should be a ProxyWindow to the same-origin window.

Observed Behavior

The event.source property value is a ProxyWindow to the cross-origin window.

Impact

A threat actor can abuse this behavior to compromise Firefox extensions and web applications that validate the origin of incoming message with the event.source property. This can lead to other client-side vulnerabilities such as Cross-Site Scriping, Prototye Pollution, DOM Clobering...

Recommendations

Block access to the Function constructor of any property of a window loading about:blank page if it will be replaced by a cross-origin document.

Flags: sec-bounty?
Group: firefox-core-security → dom-core-security
Component: Untriaged → DOM: Core & HTML
Product: Firefox → Core

I marked the first and third comments as obsolete, and did the replacement in comment 1 that was specified by comment 2. Hopefully I did that correctly.

It looks like the MDN page listens for the postMessage via this bit of code:

window.addEventListener("message", function(event) {
  if (event.source == window &&
      event.data.direction &&
      event.data.direction == "from-content-script") {
    alert("Page script received message: \"" + event.data.message + "\"");
  }
});
Component: DOM: Core & HTML → DOM: postMessage

I don't think this is postMessage specific.

  1. loc = win.location.constructor.constructor is effectively loc = win.Function. Both are supposed to fail with DOMException SecurityError and it does fail after the new window load completes, but it does not fail before that and successfully return the JS Function constructor.
  2. loc("this.foo")() now does weird thing where this is the third party window. Accessing anything e.g. this.console still throws SecurityError, but accessing postMessage is allowed just as win.postMessage is allowed, except now postMessage thinks the caller is not cross origin.

Chrome also allows the access described at 1, while the returned Function constructor is not functional at all as loc("any string")() throws TypeError saying "no access".

As such I'll pass this back to Core & HTML.

Edit: Safari also has the same behavior described at 2 except it sets event.source to null.

Component: DOM: postMessage → DOM: Core & HTML

(But perhaps it fits better in SpiderMonkey?)

This is still postMessage :) Safari behavior might be quite doable.

Component: DOM: Core & HTML → DOM: postMessage

Setting null? Why not just block such script run at all?

The recommended check when receiving a postMessage() is to check event.origin, not compare event.source. It's bad that it's misleading, but do lots of people do it this wrong way?

Observed Behavior
The event.source property value is a ProxyWindow to the cross-origin window.

Is it? Or is it rather that event.source is actually the receiver's window and nobody has access to the other window? They shouldn't be equal, but Firefox is the one that mixed it up, but it doesn't mean the attacking page has access to it. if event.source == window then the event listener hasn't gained any powers because we know window is safe.

Status: UNCONFIRMED → NEW
Ever confirmed: true
Flags: needinfo?(frsantos9989)
Keywords: csectype-spoof

peterv, are you perhaps looking into this some more? (This was discussed during DOM security meeting)

Flags: needinfo?(peterv)

Yeah, I'll take it.

Assignee: nobody → peterv
Status: NEW → ASSIGNED
Flags: needinfo?(peterv)

(In reply to Daniel Veditz [:dveditz] from comment #10)

The recommended check when receiving a postMessage() is to check event.origin, not compare event.source. It's bad that it's misleading, but do lots of people do it this wrong way?

Observed Behavior
The event.source property value is a ProxyWindow to the cross-origin window.

Is it? Or is it rather that event.source is actually the receiver's window and nobody has access to the other window? They shouldn't be equal, but Firefox is the one that mixed it up, but it doesn't mean the attacking page has access to it. if event.source == window then the event listener hasn't gained any powers because we know window is safe.

Hello, I hope all is well with everyone. :)

I do not think the question: "It's bad that it's misleading, but do lots of people do it this wrong way?" is an important question to ask here. Where does that question leads us? What can we gain from thinking about that? Would you relax on this security issue if proven that lots of people don't do it this wrong way?

People are led to implement proper BAC rules everyday from various sources. It's still the OWASP TOP 1 2021. If it's still OWASP TOP 1 when people are led to mitigate BAC issues, imagine if they were mislead.

On another note:

I did not gain access to a cross-origin page from an attacking page.
I deem the impact of confusing Firefox about who is event.source worthy of a report: Mozilla's add-ons team included an example extension that allows exploiting this issue: https://github.com/mdn/webextensions-examples/blob/main/page-to-extension-messaging/content-script.js

Flags: needinfo?(frsantos9989)
Severity: -- → S3
Priority: -- → P2

(In reply to Daniel Veditz [:dveditz] from comment #10)

The recommended check when receiving a postMessage() is to check event.origin, not compare event.source. It's bad that it's misleading, but do lots of people do it this wrong way?

event.source === window is (commonly?) used to make sure that the message came from the window itself. This is reasonable because it is usually not possible for two (cross-origin) scripts to be actively associated with the same window at the same time. Checking event.origin === origin && event.source === window would seemingly work, except in case of opaque origins, in which case the serialization of the origin is "null".

Expected Behavior

The event.source property value should be a ProxyWindow to the same-origin window.

I think that a more sensible expected behavior is to just drop the message. There is no situation where it makes sense to deliver a message from a window to the same outer window, when the inner window has changed to a cross-origin one. While a check could be added to postMessage to fix this, in theory it is possible for the navigation to occur between a postMessage call and the event dispatch.

Another option, if feasible, is to not execute JS code when content has navigated. Judging by comment #6, Chrome does this, which is why the situation does not happen in Chrome.

Hello.

How are you?

I havn't been able to escalate this. Do you have new info on your end? Thank you.

Request information to ensure the ping is received. Thank you. Have a great day. :)

Flags: needinfo?(peterv)

I think currently we don't have a conclusion about which one in comment #6 should be implemented.

From an implementation aspect, we could have a quick fix for this security bug, a solution that nulls the event.source, and then a follow-up bug for blocking script execution.

Olli, how do you think?

Flags: needinfo?(smaug)
Assignee: peterv → smaug
Flags: needinfo?(smaug)

Yes, the plan is to have the same behavior what webkit has.

If I'm reading the code correctly webkit can return non-null source if the right timing is used, and Chrome throwing exception is against the specs.

Attached file (secure) (obsolete) —

Comment on attachment 9472853 [details]
(secure)

Security Approval Request

  • How easily could an exploit be constructed based on the patch?: I think relatively easily
  • Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: Unknown
  • Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?:
  • If not all supported branches, which bug introduced the flaw?: None
  • Do you have backports for the affected branches?: No
  • If not, how different, hard to create, and risky will they be?: The code looks the same also in esr115
  • How likely is this patch to cause regressions; how much testing does it need?: This is unfortunately not-specified behavior (I'll need to file a security sensitive spec bug).
    But the situation should happen only in very rare cases.
  • Is the patch ready to land after security approval is given?: Yes
  • Is Android affected?: Yes
Attachment #9472853 - Flags: sec-approval?

Comment on attachment 9472853 [details]
(secure)

Approved to land and request uplift

Attachment #9472853 - Flags: sec-approval? → sec-approval+
Flags: needinfo?(peterv)
Pushed by opettay@mozilla.com: https://hg.mozilla.org/integration/autoland/rev/a420e4797f02 clear source browsing context early, if possible, r=nika
Backout by abutkovits@mozilla.com: https://hg.mozilla.org/integration/autoland/rev/831e5c9af61d Backed out changeset a420e4797f02 for causing failures at compile-event-handler-settings-objects.html.

Boo. I had run the tests.

Backout by ctuns@mozilla.com: https://hg.mozilla.org/mozilla-central/rev/3a265fdc9f33 Backed out changeset a420e4797f02 for causing failures at compile-event-handler-settings-objects.html.

Hello. I hope everyone is well.
How's the fix looking? Can you share code details of this bug? I would love to learn what happened on your side.

Would it be possible to write a blog post about this when it's fixed? It would be posted at https://paranoidmoth.github.io/

For the rock music fans here, paranoidmoth is a mild reference to paranoid android from radiohead.

Thanks! :)

There is an r+ patch which didn't land and no activity in this bug for 2 weeks.
:smaug, could you have a look please?
If you still have some work to do, you can add an action "Plan Changes" in Phabricator.
For more information, please visit BugBot documentation.

Flags: needinfo?(smaug)
Flags: needinfo?(nika)
Flags: needinfo?(nika)

Hello. I hope everything is well.

Any feedback on this bug?

Need to figure out how to schedule the postMessage task in a bit different way so that we don't break that test.

Flags: needinfo?(smaug)

Hello. I hope you are well.

Have you figured out how to schedule the postMessage task?

I would love to dive deep into your progress.

Thanks. :)

Flags: needinfo?(smaug)

Olli, I take the bug to investigate the test failures.

Assignee: smaug → echuang
Flags: needinfo?(smaug) → needinfo?(echuang)
Attachment #9472853 - Attachment description: Bug 1880429, clear source browsing context early, if possible, r=nika → (secure)

The test failures are clear. We need to do some interesting scheduling related magic.

Comment on attachment 9472853 [details]
(secure)

Revision D242075 was moved to bug 2003504. Setting attachment 9472853 [details] to obsolete.

Attachment #9472853 - Attachment is obsolete: true
Status: ASSIGNED → RESOLVED
Closed: 9 months ago
Flags: needinfo?(echuang)
Resolution: --- → WORKSFORME

I believe the patch was moved to a public bug, which is fine, but since it hasn't landed this one isn't fixed, is it?

Making this "depend on" the public bug, but reopening so we can correctly track status (advisories, bounties) of the security bug separately.

Status: RESOLVED → REOPENED
Depends on: 2003504
Resolution: WORKSFORME → ---
Whiteboard: [fixing in 2003504]

Bug 2003504 was resolved. Can this bug be closed as FIXED now?

Flags: needinfo?(echuang)

Yes, I think so.

Flags: needinfo?(echuang)
Status: REOPENED → RESOLVED
Closed: 9 months ago6 months ago
Resolution: --- → FIXED
Target Milestone: --- → 150 Branch
Group: dom-core-security → core-security-release
Flags: sec-bounty? → sec-bounty+
QA Whiteboard: [sec] [qa-triage-done-c151/b150] [qa-verif-needed-c151/b150]
Flags: qe-verify+
Whiteboard: [fixing in 2003504] → [fixing in 2003504][adv-main150+]
Alias: CVE-2026-6755
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: