Compromised content process can spoof URL bar and TLS security indicators via RecvOnLocationChange + RecvUpdateDocumentSecurityInfo
Categories
(Core :: DOM: Security, defect)
Tracking
()
People
(Reporter: mirzashihab2, Unassigned)
Details
(Keywords: ai-involved, reporter-external)
Attachments
(1 file)
|
13.93 KB,
text/plain
|
Details |
Compromised content process can spoof URL bar and TLS security indicators via RecvOnLocationChange + RecvUpdateDocumentSecurityInfo
Summary
A compromised content process can fully spoof Firefox's URL bar and TLS security indicators (padlock icon, EV certificate badge, certificate details) by sending two IPC messages with no validation:
PWindowGlobal::UpdateDocumentSecurityInfo— replaces TLS security info with forged data (zero validation)PBrowser::OnLocationChange— sets the displayed URL to any URI (URI itself never validated)
Combined, these allow a compromised renderer on an attacker-controlled HTTPS site to display https://bank.com in the URL bar with a green padlock and forged EV certificate details, while the actual page content is the attacker's phishing form. This is a complete bypass of Fission's browser trust indicator isolation model.
Prerequisite: The attacker's own site must use HTTPS (free via Let's Encrypt). This is because the lock icon decision reads GetIsSecure() (based on the WindowGlobal's document URI scheme), which is already true for any HTTPS page.
Affected Component
dom/ipc/BrowserParent.cpp, lines 2981-3034 (RecvOnLocationChange— URI not validated)dom/ipc/WindowGlobalParent.cpp, lines 729-733 (RecvUpdateDocumentSecurityInfo— zero validation)docshell/base/CanonicalBrowsingContext.cpp(SetCurrentRemoteURI— direct assignment, zero checks)security/manager/ssl/nsSecureBrowserUI.cpp, lines 51-85 (RecomputeSecurityFlags— reads forged mSecurityInfo)browser/base/content/browser-siteIdentity.js(displays lock icon from forged data)
Severity
sec-critical (S1) — A compromised content process can perfectly spoof the browser's URL bar and all TLS security indicators, enabling phishing attacks that are visually indistinguishable from legitimate HTTPS sites. This directly violates the Fission security model, which is designed to prevent a compromised renderer from spoofing trusted browser UI.
Root Cause
Issue 1: RecvOnLocationChange does not validate the URI
// dom/ipc/BrowserParent.cpp:2981-3034
IPCResult BrowserParent::RecvOnLocationChange(
const WebProgressData& aWebProgressData, const RequestData& aRequestData,
nsIURI* aLocation, /* ... */
const Maybe<WebProgressLocationChangeData>& aLocationChangeData) {
// ReceiveProgressListenerData checks browsing context ownership only
if (!ReceiveProgressListenerData(...)) { return IPC_OK(); }
// Content-controlled URI stored directly with NO validation
browsingContext->SetCurrentRemoteURI(aLocation);
// Content-controlled principal passed to browser element
if (aLocationChangeData.isSome()) {
browser->UpdateForLocationChange(
aLocation, ...,
aLocationChangeData->contentPrincipal(), // content-controlled
aLocationChangeData->contentPartitionedPrincipal(), ...);
}
// Fires OnLocationChange to web progress listeners (updates URL bar)
listener->OnLocationChange(listener, request, aLocation, aFlags);
// Updates security state using the forged mSecurityInfo
if (browsingContext->IsTopContent() &&
!(aFlags & LOCATION_CHANGE_SAME_DOCUMENT)) {
browsingContext->UpdateSecurityState();
}
}
SetCurrentRemoteURI (CanonicalBrowsingContext.cpp) has zero checks:
void CanonicalBrowsingContext::SetCurrentRemoteURI(nsIURI* aCurrentRemoteURI) {
MOZ_ASSERT(!GetDocShell());
mCurrentRemoteURI = aCurrentRemoteURI; // Direct assignment
}
GetCurrentURI() returns this for remote (Fission) browsing contexts:
already_AddRefed<nsIURI> CanonicalBrowsingContext::GetCurrentURI() const {
if (nsIDocShell* docShell = GetDocShell()) { /* local path */ }
else { currentURI = mCurrentRemoteURI; } // Returns spoofed URI
}
The URL bar (UrlbarInput.mjs) reads gBrowser.currentURI which calls GetCurrentURI().
Issue 2: RecvUpdateDocumentSecurityInfo has zero validation
// dom/ipc/WindowGlobalParent.cpp:729-733
IPCResult WindowGlobalParent::RecvUpdateDocumentSecurityInfo(
nsITransportSecurityInfo* aSecurityInfo) {
mSecurityInfo = aSecurityInfo; // Zero validation
return IPC_OK();
}
Issue 3: Lock icon reads the forged security info
// security/manager/ssl/nsSecureBrowserUI.cpp:51-85
void nsSecureBrowserUI::RecomputeSecurityFlags() {
RefPtr<WindowGlobalParent> win = GetCurrentWindow();
mState = STATE_IS_INSECURE;
if (win && win->GetIsSecure()) { // true if documentURI scheme is "https"
securityInfo = win->GetSecurityInfo(); // returns forged mSecurityInfo
if (securityInfo) {
securityInfo->GetSecurityState(&mState); // STATE_IS_SECURE from forged data
securityInfo->GetIsExtendedValidation(&isEV); // true from forged data
if (isEV) {
mState |= STATE_IDENTITY_EV_TOPLEVEL; // EV badge enabled
}
}
}
}
GetIsSecure() (WindowGlobalActor.cpp:136-138) checks if the WindowGlobal's documentURI scheme is "https". Since the attacker's own site is HTTPS, this is already true — the attacker does not need to forge this field.
Issue 4: Parent process never cross-checks
In Fission, the parent process handles network requests via DocumentChannel/DocumentLoadListener. It has the real URI and real TLS info during the request. But:
- The parent-side TLS info is not preserved for later comparison
SetCurrentRemoteURIis not compared against the DocumentChannel URImSecurityInfois not compared against the network layer's TLS info- The content process's report is accepted as authoritative for both the URI and security state
Attack Flow
Prerequisite
Attacker controls https://evil.com (free Let's Encrypt certificate).
Step 1: User visits attacker's HTTPS site
User navigates to https://evil.com. The WindowGlobal is created with:
documentURI = "https://evil.com"→GetIsSecure() = truemSecurityInfo= evil.com's legitimate TLS info
Content process is compromised (e.g., via renderer exploit).
Step 2: Forge TLS security info
Compromised content process sends:
PWindowGlobal::UpdateDocumentSecurityInfo(
securityInfo = {
securityState: STATE_IS_SECURE,
isExtendedValidation: true,
serverCert: {
commonName: "www.bank.com",
organization: "Bank of America, Inc.",
issuerOrganization: "DigiCert Inc",
sha256Fingerprint: "AA:BB:CC:...(forged)..."
},
cipherName: "TLS_AES_256_GCM_SHA384",
protocolVersion: TLS_VERSION_1_3
}
)
Parent: mSecurityInfo = aSecurityInfo; — stored with zero checks.
Step 3: Spoof URL bar and trigger security state update
Compromised content process sends:
PBrowser::OnLocationChange(
browsingContext = <this tab's top-level BC>,
location = "https://bank.com/login",
flags = 0, // NOT LOCATION_CHANGE_SAME_DOCUMENT — triggers UpdateSecurityState
locationChangeData = {
contentPrincipal: <principal for https://bank.com>,
title: "Bank of America - Sign In",
documentURI: "https://bank.com/login"
}
)
Parent process executes:
SetCurrentRemoteURI("https://bank.com/login")— URL stored (no validation)UpdateForLocationChange(...)— browser element updated with spoofed dataOnLocationChange(...)→ URL bar showshttps://bank.com/loginUpdateSecurityState()→RecomputeSecurityFlags():GetIsSecure()= true (documentURIhttps://evil.comis HTTPS)GetSecurityInfo()= forged mSecurityInfo (bank.com EV cert)GetSecurityState()→STATE_IS_SECUREGetIsExtendedValidation()→ truemState |= STATE_IDENTITY_EV_TOPLEVEL
- Lock icon: green padlock with EV badge
Result
User sees:
- URL bar:
https://bank.com/login - Lock icon: Green padlock
- EV badge: "Bank of America, Inc. [US]"
- Certificate viewer: Forged certificate details for bank.com
- Page content: Attacker's phishing form
Attack requires NO user interaction beyond having visited the attacker's HTTPS page.
Impact
- Perfect phishing: Visually indistinguishable from real HTTPS site with EV certificate
- Credential theft: User enters passwords for spoofed site
- Principal spoofing:
contentPrincipalinUpdateForLocationChangeis content-controlled - Undetectable: No visual indicator that anything is wrong — all trust signals are forged
- Low barrier: Attacker only needs a free Let's Encrypt cert for their own site
- All platforms: Affects all platforms with Fission enabled (default on all desktop platforms)
Mitigations Checked and Found Insufficient
| Mitigation | Result |
|---|---|
ReceiveProgressListenerData (BrowserParent.cpp:3120) |
Checks browsing context ownership only, NOT the URI |
SetCurrentRemoteURI (CanonicalBrowsingContext.cpp) |
Direct assignment, MOZ_ASSERT only |
| DocumentChannel parent-side URI | Never compared against content-reported URI |
| TLS info from network layer | Parent does not store its own TLS info for comparison |
GetIsSecure() (WindowGlobalActor.cpp:137) |
Checks documentURI scheme — true for attacker's HTTPS site |
ValidatePrincipal in RecvNewWindowGlobal (BrowserParent.cpp:1401) |
Non-fatal: logs + MOZ_ASSERT only, execution continues |
| Fission process isolation | Content process reports to its OWN tab — ownership check passes |
Suggested Fix
Fix 1: Parent-authoritative URI for URL bar
The parent process should set the URL bar URI from its own navigation data (DocumentChannel), not from the content process. RecvOnLocationChange should only be allowed to report same-origin URIs matching the navigation the parent initiated.
Fix 2: Parent-controlled security info
Remove RecvUpdateDocumentSecurityInfo. The parent should derive TLS security info from the DocumentChannel's actual network connection, not from content process reports.
Fix 3: Cross-check URI against navigation
If RecvOnLocationChange reports a URI that doesn't match the last parent-initiated navigation for this browsing context, reject with IPC_FAIL.
Files
| File | Line | Role |
|---|---|---|
dom/ipc/BrowserParent.cpp |
2981-3034 | RecvOnLocationChange — URI not validated |
dom/ipc/BrowserParent.cpp |
3120-3170 | ReceiveProgressListenerData — ownership check only |
dom/ipc/BrowserParent.cpp |
1401-1403 | RecvNewWindowGlobal — principal validation non-fatal |
dom/ipc/WindowGlobalParent.cpp |
729-733 | RecvUpdateDocumentSecurityInfo — zero validation |
dom/ipc/WindowGlobalActor.cpp |
136-138 | GetIsSecure — checks documentURI scheme |
docshell/base/CanonicalBrowsingContext.cpp |
SetCurrentRemoteURI |
Direct URI assignment |
docshell/base/CanonicalBrowsingContext.cpp |
GetCurrentURI |
Returns spoofed URI for remote contexts |
security/manager/ssl/nsSecureBrowserUI.cpp |
51-85 | Reads forged security info for lock icon |
browser/base/content/browser-siteIdentity.js |
713-720 | Displays lock icon from forged data |
browser/components/urlbar/UrlbarInput.mjs |
825 | URL bar reads gBrowser.currentURI |
| Reporter | ||
Updated•7 months ago
|
Updated•7 months ago
|
Updated•7 months ago
|
Updated•7 months ago
|
Updated•1 month ago
|
Description
•