Closed Bug 2063490 Opened 3 months ago Closed 3 months ago

RecvReloadWithHttpsOnlyException uses Top() unconditionally — sub-frame error page can downgrade top-level HTTPS to HTTP

Categories

(Core :: DOM: Security, defect)

defect

Tracking

()

RESOLVED DUPLICATE of bug 2014104

People

(Reporter: boiprcvn156, Unassigned)

Details

(Keywords: reporter-external)

Attachments

(1 file)

5.10 KB, application/octet-stream
Details
Attached file https-only-poc.py —

WindowGlobalParent::RecvReloadWithHttpsOnlyException() (dom/ipc/WindowGlobalParent.cpp:1644) uses BrowsingContext()->Top() unconditionally — it never checks whether the calling WindowGlobal actually belongs to the top-level frame. A sub-frame showing about:httpsonlyerror can call document.reloadWithHttpsOnlyException() and the handler will downgrade the top-level page from HTTPS to HTTP and add a session-duration HTTPS-Only exception for the top-level domain.

This is a logic bug, not a compromised-renderer scenario. The WebIDL gate CallerIsTrustedAboutHttpsOnlyError (Document.cpp:1505) checks IsAboutErrorPage(win, "httpsonlyerror"), which legitimately passes for a sub-frame that's on the error page. The problem is the handler then operates on Top() instead of the calling frame's own BC.

Root cause

// dom/ipc/WindowGlobalParent.cpp:1644
mozilla::ipc::IPCResult WindowGlobalParent::RecvReloadWithHttpsOnlyException() {
  // Gets the TOP-LEVEL URI, not the calling frame's URI
  nsCOMPtr<nsIURI> currentURI = BrowsingContext()->Top()->GetCurrentURI();

  // Creates HTTP version of the TOP-LEVEL URI
  (void)NS_MutateURI(innerURI).SetScheme("http"_ns).Finalize(getter_AddRefs(newURI));

  // Adds exception for the TOP-LEVEL domain
  rv = permMgr->AddFromPrincipal(principal, "https-only-load-insecure"_ns,
      nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW_SESSION,
      nsIPermissionManager::EXPIRE_SESSION, 0);

  // Navigates the TOP-LEVEL BC with system principal
  loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
  RefPtr<CanonicalBrowsingContext> topBC = BrowsingContext()->Top();
  topBC->LoadURI(loadState, true);
}

No check: if (BrowsingContext() != BrowsingContext()->Top()) return IPC_FAIL(...);

Reproduction

Tested on debug build 155.0a1 with Marionette. Needs dom.security.https_only_mode = true.

  1. Enable HTTPS-Only mode
  2. Navigate to a real HTTPS site: https://example.org/
  3. Inject an iframe pointing to an HTTP-only URL (something with no TLS):
    let f = document.createElement('iframe');
    f.src = 'http://SOMEHOST:9999/anything';  // port with no TLS listener
    document.body.appendChild(f);
    
  4. HTTPS-Only mode upgrades the iframe load → TLS handshake fails → about:httpsonlyerror appears in the iframe
  5. Switch into the iframe context and call:
    document.reloadWithHttpsOnlyException();
    
  6. Check the top-level page: it's now on http://example.org/
  7. Check permissions: https-only-load-insecure session permission was added for example.org

What I observed:

  • BEFORE: location.protocol = "https:", permission = 0 (no exception)
  • AFTER: location.protocol = "http:", permission = 9 (LOAD_INSECURE_ALLOW_SESSION)

The top-level page was silently downgraded from HTTPS to HTTP, and the session exception means it stays that way for the rest of the browsing session.

Why this matters without renderer compromise

A page can embed iframes to HTTP-only resources. When HTTPS-Only mode shows the error page in those iframes, the "Continue to HTTP Site" button in any sub-frame error page will downgrade the top-level page — not just the iframe. A user who thinks they're allowing HTTP for some embedded resource is actually downgrading their main page.

With a compromised renderer it's even simpler — just call the method directly from the sub-frame, no user interaction needed.

Limitation

dom.security.https_only_mode defaults to false (opt-in). Only affects users who enabled it.

Suggested fix

Add at the top of RecvReloadWithHttpsOnlyException:

if (BrowsingContext() != BrowsingContext()->Top()) {
  return IPC_FAIL(this, "Only top-level frames can add HTTPS-Only exceptions");
}

Thank you for the report. This issue has been determined to be a duplicate of bug 2014104.

Both report the same defect: WindowGlobalParent::RecvReloadWithHttpsOnlyException() uses BrowsingContext()->Top() unconditionally with no IsTop() guard, so a sub-frame showing about:httpsonlyerror can add a session https-only-load-insecure exception for the top-level origin and force the top-level page to reload over HTTP (system-principal navigation). Same handler, same missing check, same symptom.

Group: core-security → dom-core-security
Status: UNCONFIRMED → RESOLVED
Closed: 3 months ago
Duplicate of bug: 2014104
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: