BroadcastChannel: Missing ValidatePrincipal allows cross-origin message injection and eavesdropping from compromised renderer
Categories
(Core :: DOM: postMessage, defect)
Tracking
()
People
(Reporter: fb.secresearch, Unassigned)
Details
(Keywords: reporter-external)
Attachments
(1 file)
|
2.07 KB,
application/zip
|
Details |
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:
- Build Firefox with the attached poc-hook.patch.
- Add to /etc/hosts:
127.0.0.1 attacker.testand127.0.0.1 victim.test - Serve the attached PoC files:
python3 -m http.server 8080 - Start Firefox:
POC_BC_TARGET="http://victim.test:8080" ./firefox -profile /tmp/test-profile - Open Tab 1:
http://victim.test:8080/victim.html - Open Tab 2:
http://attacker.test:8080/attacker.html - 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:8080sends messages that arrive in victim tab onvictim.test:8080 - Victim's
event.originshowshttp://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.
Comment 1•7 months ago
|
||
Looks like a dupe of bug 2016925
Updated•7 months ago
|
Updated•1 month ago
|
Description
•