Content process can disable HTTPS-First for an arbitrary host
Categories
(Core :: DOM: Security, defect)
Tracking
()
People
(Reporter: vanirxxx, Unassigned)
Details
(Keywords: reporter-external, Whiteboard: [client-bounty-form])
Attachments
(1 file)
|
67.19 KB,
application/x-zip-compressed
|
Details |
Summary
A content process reports its own top-level location to the parent, unvalidated, and the parent stores it. A second message then asks the parent to add an HTTPS-Only/HTTPS-First exception — it carries no arguments, so the parent takes the target host from that stored value. The exception it writes, LOAD_INSECURE_ALLOW_SESSION, is honoured in every upgrade mode, including HTTPS-First, which is on by default. A content process can therefore turn off HTTPS upgrading for any origin, for the rest of the session, without user interaction. Demonstrated: http://victim.example — never visited — stops being upgraded.
Threat model
An attacker with code execution in an ordinary content process. Page JS alone cannot do this: the location a normal renderer reports comes from its own docshell, and the exception message is sent only by the HTTPS-Only error page.
Affects
Firefox Nightly 154.0a1, self-built from mozilla-central (ASan+fuzzing), BuildID 20260716060151, Linux x86_64; line numbers are against changeset 79223b6429f6. Default configuration: dom.security.https_first is true; dom.security.https_only_mode is false and is not required. No platform conditional in the chain.
Root cause
1. The reported location is stored without validation. dom/ipc/BrowserParent.cpp:3023:
browsingContext->SetCurrentRemoteURI(aLocation);
aLocation is the URI the child sent. ReceiveProgressListenerData above it confirms the browsing context is one this BrowserParent manages, but nothing checks that the reported URI is one the sending process may claim — a process holding http://al.com can report https://victim.example.
2. The exception message carries no arguments, so it inherits that value. dom/ipc/PWindowGlobal.ipdl:226:
async ReloadWithHttpsOnlyException();
dom/ipc/WindowGlobalParent.cpp:1653:
nsCOMPtr<nsIURI> currentURI = BrowsingContext()->Top()->GetCurrentURI();
The handler rewrites that URI's scheme to http and, at dom/ipc/WindowGlobalParent.cpp:1694, grants the permission:
rv = permMgr->AddFromPrincipal(
principal, "https-only-load-insecure"_ns,
nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW_SESSION,
nsIPermissionManager::EXPIRE_SESSION, 0);
There is no user-gesture requirement, and no check that the current URI is related to the sending window's principal.
3. The value written is not scoped to HTTPS-Only. dom/security/nsHTTPSOnlyUtils.cpp:709:
return perm == nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW ||
perm == nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW_SESSION ||
(checkForHTTPSFirst &&
perm == nsIHttpsOnlyModePermission::HTTPSFIRST_LOAD_INSECURE_ALLOW);
Only HTTPSFIRST_LOAD_INSECURE_ALLOW is qualified by checkForHTTPSFirst. LOAD_INSECURE_ALLOW_SESSION — the value step 2 writes — exempts the origin in all upgrade modes, so the grant intended for opt-in HTTPS-Only also disables the default-on HTTPS-First upgrade.
Steps to reproduce
From a content process holding http://al.com:8199: send OnLocationChange for its own top-level browsing context with aLocation = https://victim.example/, then send ReloadWithHttpsOnlyException on its window global. Then load http://victim.example/.
Actual result
The parent grants the exception for a host the browser never visited, and the next load of that host is not upgraded:
[FORGE-LOC] reporting https://victim.example/ instead of about:blank
[FORGE-EXC] sending ReloadWithHttpsOnlyException
[PARENT-GRANT] https-only-load-insecure for http://victim.example
[EXEMPT] upgrade skipped for http://victim.example (mode=2 perm=9)
mode=2 is HTTPS_FIRST_MODE; perm=9 is LOAD_INSECURE_ALLOW_SESSION. Reproduced 3/3. With the two forged messages removed and nothing else changed, neither line appears at all (0 occurrences of both).
Expected result
The target of an HTTPS-Only/HTTPS-First exception must be derived from state the parent owns for the sending window — its document principal — not from a location string the same process reported earlier, and the grant should be scoped to the mode it was issued for.
Proof of concept
poc/ — two env-gated hunks in the child modelling the compromised renderer (report a chosen location; send the exception message), two read-only prints in the parent (the permission grant, and the point where the upgrade is skipped), the test page, and RESULTS.txt with the attack next to its control.
Suggested fix
- Validate in
RecvOnLocationChangethat the reported URI is loadable by the sending process, as other content→parent URI handlers do. - Derive the exception target in
RecvReloadWithHttpsOnlyExceptionfromDocumentPrincipal()rather than fromGetCurrentURI(), and require a user gesture. - Gate
LOAD_INSECURE_ALLOW_SESSIONon the mode it was granted in, the wayHTTPSFIRST_LOAD_INSECURE_ALLOWalready is.
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Description
•