Closed Bug 2026011 Opened 7 months ago Closed 7 months ago

BroadcastChannel: Missing ValidatePrincipal allows cross-origin message injection and eavesdropping from compromised renderer

Categories

(Core :: DOM: postMessage, defect)

defect

Tracking

()

RESOLVED DUPLICATE of bug 2016925

People

(Reporter: fb.secresearch, Unassigned)

Details

(Keywords: reporter-external)

Attachments

(1 file)

2.07 KB, application/zip
Details
Attached file poc-52.zip —

User Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36

Steps to reproduce:

Audited the BroadcastChannel IPC path for missing origin validation. Found that BackgroundParentImpl::RecvPBroadcastChannelConstructor registers the actor immediately on the PBackground thread, then dispatches CheckPrincipalRunnable asynchronously to the Main Thread. The runnable validates PrincipalInfo↔origin string consistency but does NOT call ContentParent::ValidatePrincipal() to verify the content process is authorized to claim that origin.

A compromised content process can call SendPBroadcastChannelConstructor with a forged PrincipalInfo and origin string for any target origin (e.g. https://gmail.com). Because both are forged consistently, CheckPrincipalRunnable passes. The actor is permanently registered in the BroadcastChannelService under the victim's origin.

Prerequisite: compromised content process.

To demonstrate, a patched Firefox build adds a hook in BroadcastChannel::Constructor that replaces the origin and PrincipalInfo before calling SendPBroadcastChannelConstructor:

// In BroadcastChannel::Constructor, before SendPBroadcastChannelConstructor:
origin.Assign("https://victim.test");
storagePrincipalInfo = ContentPrincipalInfo(
    attrs, "https://victim.test"_ns, "https://victim.test/"_ns,
    Nothing(), "victim.test"_ns);

Steps:

  1. Build Firefox with the attached poc-hook.patch.
  2. Add to /etc/hosts: 127.0.0.1 attacker.test and 127.0.0.1 victim.test
  3. Serve the attached PoC files: python3 -m http.server 8080
  4. Start Firefox: POC_BC_TARGET="http://victim.test:8080" ./firefox -profile /tmp/test-profile
  5. Open Tab 1: http://victim.test:8080/victim.html
  6. Open Tab 2: http://attacker.test:8080/attacker.html
  7. Observe the victim tab.

The same result can be achieved without a patched build by a compromised renderer directly calling PBackgroundChild::SendPBroadcastChannelConstructor() with crafted arguments.

Attachments:

  • poc-hook.patch — BroadcastChannel.cpp hook (~50 lines)
  • attacker.html — page on attacker.test that joins victim's BroadcastChannel
  • victim.html — legitimate page using BroadcastChannel for tab sync
  • Tested on mozilla-central, March 2026

Actual results:

The parent process accepted the forged BroadcastChannel registration. CheckPrincipalRunnable passed because the forged PrincipalInfo and origin string are consistent. No KillHard was triggered.

The validation path:

BackgroundParentImpl::RecvPBroadcastChannelConstructor (BackgroundParentImpl.cpp:788)
  → new BroadcastChannelParent(originChannelKey)
    → mService->RegisterActor(this, mOriginChannelKey)  // IMMEDIATE, on PBackground thread
  → NS_DispatchToMainThread(CheckPrincipalRunnable)     // ASYNC, on Main Thread
    → CheckPrincipalRunnable::Run()
      → PrincipalInfoToPrincipal(mPrincipalInfo)  // Creates principal from forged info
      → principal->GetOrigin(origin)               // Gets origin from forged principal
      → origin == mOrigin ? PASS : KillHard        // Consistent forgery → PASS
      → [ValidatePrincipal is NEVER called]         // Missing check

Result:

  • Attacker tab on attacker.test:8080 sends messages that arrive in victim tab on victim.test:8080
  • Victim's event.origin shows http://victim.test:8080 (the forged origin, NOT attacker's real origin)
  • Attacker can also RECEIVE messages sent by victim (eavesdropping)
  • Connection persists as long as the attacker's tab remains open (no repeated exploitation needed)

This is two bugs in one:

Bug A — Missing ValidatePrincipal: CheckPrincipalRunnable only validates internal consistency (PrincipalInfo matches origin string) but never calls ContentParent::ValidatePrincipal() to verify the content process is authorized to claim that origin. A compromised renderer with consistent forgery is permanently accepted.

Bug B — TOCTOU Race: Even without consistent forgery, the actor is registered on the PBackground thread BEFORE CheckPrincipalRunnable runs on the Main Thread. A PostMessage sent immediately after construction is delivered before validation. The XXX comment at BackgroundParentImpl.cpp:802 acknowledges this could be fixed.

Expected results:

The parent should reject the forged BroadcastChannel registration. Two fixes needed:

Fix 1 (critical): Call ValidatePrincipal in CheckPrincipalRunnable::Run():

// In CheckPrincipalRunnable::Run(), after the PrincipalInfo↔origin check:
RefPtr<ContentParent> contentParent = BackgroundParent::GetContentParent(mActor->Manager());
if (contentParent && !contentParent->ValidatePrincipal(principal)) {
    contentParent->KillHard("BroadcastChannel: process not authorized for claimed origin");
    return NS_OK;
}

Fix 2 (defense-in-depth): Add mValidated state flag to BroadcastChannelParent. Guard RecvPostMessage to reject messages until validation completes:

if (!mValidated) {
    return IPC_FAIL(this, "Message before validation complete");
}

Impact: Cross-origin BroadcastChannel message injection and eavesdropping. Any web application using BroadcastChannel for cross-tab communication is affected.

Looks like a dupe of bug 2016925

Group: firefox-core-security → dom-core-security
Status: UNCONFIRMED → RESOLVED
Closed: 7 months ago
Component: Untriaged → DOM: postMessage
Duplicate of bug: 2016925
Product: Firefox → Core
Resolution: --- → DUPLICATE
Group: dom-core-security
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: