Closed Bug 2026588 Opened 3 months ago Closed 3 months ago

Compromised content process can add persistent TLS certificate overrides for arbitrary hostnames via AddCertException IPC

Categories

(Core :: DOM: Content Processes, defect)

defect

Tracking

()

RESOLVED DUPLICATE of bug 2013800

People

(Reporter: mirzashihab2, Unassigned)

Details

(Keywords: reporter-external, Whiteboard: [client-bounty-form])

Attachments

(2 files)

Compromised content process can add persistent TLS certificate overrides for arbitrary hostnames via AddCertException IPC

Summary

The parent-side IPC handler ContentParent::RecvAddCertException accepts a hostname, port, and X.509 certificate from a content process and stores a permanent TLS certificate override, with no validation of any parameter. A compromised content process can silently add overrides for arbitrary domains (e.g., bankofamerica.com), causing Firefox to accept an attacker-controlled certificate for those domains on all future connections. The override persists to disk and survives browser restarts.

This violates the Fission security boundary: the parent process performs a privileged, persistent, security-critical action based solely on unvalidated content-process input.

Affected Component

  • dom/ipc/ContentParent.cpp, lines 6540-6554 (RecvAddCertException)
  • dom/ipc/PContent.ipdl, line 1609 (AddCertException message definition)

Severity

sec-high -- Sandbox escape primitive. Converts a content-process compromise into persistent MITM capability against non-HSTS-preloaded HTTPS sites.

Root Cause

// dom/ipc/ContentParent.cpp:6540-6554
mozilla::ipc::IPCResult ContentParent::RecvAddCertException(
    nsIX509Cert* aCert, const nsACString& aHostName, int32_t aPort,
    const OriginAttributes& aOriginAttributes, bool aIsTemporary,
    AddCertExceptionResolver&& aResolver) {
  nsCOMPtr<nsICertOverrideService> overrideService =
      do_GetService(NS_CERTOVERRIDE_CONTRACTID);
  if (!overrideService) {
    aResolver(NS_ERROR_FAILURE);
    return IPC_OK();
  }
  nsresult rv = overrideService->RememberValidityOverride(
      aHostName, aPort, aOriginAttributes, aCert, aIsTemporary);
  aResolver(rv);
  return IPC_OK();
}

Every parameter -- the certificate, hostname, port, origin attributes, and persistence flag -- comes entirely from the content process. The handler performs:

  • No ValidatePrincipal() check (contrast with RecvAutomaticStorageAccessPermissionCanBeGranted at line 6557 in the same file, which does call ValidatePrincipal)
  • No check that the content process has any relationship to the hostname
  • No check that the content process is displaying a certificate error page
  • No user gesture or interaction requirement
  • No IPC_FAIL on any condition

The IPDL definition (PContent.ipdl:1609) has no process-type annotations or restrictions. Any web content process can send this message.

IPC Serialization Path

The nsIX509Cert parameter is serialized as raw DER bytes over IPC:

  • Write (ipc/glue/TransportSecurityInfoUtils.cpp:45-54): Calls aParam->SerializeToIPC(aWriter) which writes raw DER bytes.
  • Read (ipc/glue/TransportSecurityInfoUtils.cpp:56-76): Creates new nsNSSCertificate() and calls DeserializeFromIPC(aReader).
  • Deserialize (security/manager/ssl/nsNSSCertificate.cpp:638-658): Reads a hasCert boolean and then raw DER bytes via ReadParam(aReader, &mDER). No validation of the certificate content.

A compromised content process provides arbitrary DER bytes that become the trusted certificate.

Legitimate Code Path

The intended caller is Document::AddCertException (dom/base/Document.cpp:1646-1661), invoked when a user clicks "Accept the Risk and Continue" on the about:certerror page. The WebIDL method is gated by [Func="Document::CallerIsTrustedAboutCertError"] (dom/webidl/Document.webidl:319), which only exposes the method on about:certerror documents.

However, a compromised content process bypasses the DOM layer entirely and calls ContentChild::SendAddCertException() directly with arbitrary parameters. The parent handler has no independent validation.

Downstream Override Service

nsCertOverrideService::RememberValidityOverride (security/manager/ssl/nsCertOverrideService.cpp:380-410) computes the SHA-256 fingerprint of whatever certificate it receives, stores it keyed by hostname:port, and with aIsTemporary=false writes it to cert_override.txt on disk. The only input validation is that the hostname is non-empty/ASCII and the port is >= -1.

Partial Mitigation: HSTS Preload and Static Pins

At TLS connection time (not at override storage time), OverrideAllowedForHost (SSLServerCertVerification.cpp:309-372) checks whether the target host is HSTS-preloaded or statically pinned. If so, the stored override is not applied.

This protects a subset of sites. Spot-checking against the preload list (nsSTSPreloadList.inc, ~166k entries):

Site HSTS Preloaded Override effective?
facebook.com Yes No
twitter.com Yes No
github.com Yes No
google.com No Yes
paypal.com No Yes
bankofamerica.com No Yes
chase.com No Yes
wellsfargo.com No Yes
amazon.com No Yes
microsoft.com No Yes

Most banking, healthcare, enterprise, and government sites are not HSTS-preloaded. Dynamic HSTS (via response header) only protects if the user has previously visited the site and the HSTS cache entry hasn't expired. The override is stored regardless -- it silently waits for any non-HSTS connection to the target.

Exploitation Scenario

  1. Attacker exploits a memory corruption bug to compromise a content process.
  2. Attacker generates a self-signed certificate for bankofamerica.com (any DER bytes work).
  3. Attacker calls SendAddCertException(cert, "bankofamerica.com", 443, {}, false).
  4. Parent stores the override permanently to cert_override.txt. No prompt, no UI indication.
  5. Attacker (or a network-level collaborator) performs MITM on the victim's connection to bankofamerica.com using the matching self-signed certificate.
  6. Firefox silently accepts the attacker's certificate. All credentials, session tokens, and data are captured.
  7. The override persists across browser restarts. The attacker can repeat for unlimited domains.

Proof of Concept

See attached files:

  • poc_setup.sh -- Generates a self-signed certificate and starts a local HTTPS server on port 8443.
  • poc_add_override.js -- Browser Console script that adds a cert override for localhost:8443 by calling the same RememberValidityOverride() that RecvAddCertException calls, then verifies the override was stored.

Steps to reproduce

  1. Run bash poc_setup.sh (leave the server running).
  2. Open Firefox, navigate to https://localhost:8443. Observe: certificate error (SEC_ERROR_UNKNOWN_ISSUER).
  3. Open about:config, set devtools.chrome.enabled to true.
  4. Open the Browser Console (Ctrl+Shift+J -- the browser-wide console, not the per-tab web console).
  5. Paste the contents of poc_add_override.js and press Enter.
  6. Navigate to https://localhost:8443 again. Observe: no certificate error. The page loads with the self-signed certificate accepted silently.
  7. Restart Firefox. Navigate to https://localhost:8443 again. Observe: still no error. The override persisted to disk.

The PoC runs in the parent process via the Browser Console, calling the identical nsICertOverrideService::RememberValidityOverride() function. The IPC handler adds zero additional validation beyond this call, so the effect is identical to a compromised content process sending SendAddCertException.

Precedent

Bug 1783504 ("Compromised content process can UpdateDocumentURI") is the same vulnerability class: a parent-side IPC handler that performs a privileged action using unvalidated content-process input. That bug was fixed as a security issue.

Suggested Fix

Option A -- Add validation to the handler:

 mozilla::ipc::IPCResult ContentParent::RecvAddCertException(
     nsIX509Cert* aCert, const nsACString& aHostName, int32_t aPort,
     const OriginAttributes& aOriginAttributes, bool aIsTemporary,
     AddCertExceptionResolver&& aResolver) {
+  if (!aCert || aHostName.IsEmpty()) {
+    return IPC_FAIL(this, "Invalid cert exception parameters");
+  }
+  // Verify the content process has a BrowsingContext with a document
+  // from this hostname that is in a certificate error state.
+  bool hasMatchingCertError = false;
+  for (const auto& bt : ManagedPBrowserParent()) {
+    // Check managed BrowsingContexts for matching hostname + cert error state
+  }
+  if (!hasMatchingCertError) {
+    return IPC_FAIL(this, "No matching cert error for this host");
+  }

Option B -- Move cert exception handling to the parent process entirely. The about:certerror page could communicate with the parent via a JSActor message that the parent validates against its own knowledge of which tabs have certificate errors, rather than using a general-purpose IPC message with content-controlled parameters.

Affected Versions

  • Firefox Nightly, Beta, Release (all current)
  • Firefox ESR 128+
  • Thunderbird (all versions using the same Gecko engine)

Files

File Role
dom/ipc/ContentParent.cpp:6540-6554 Vulnerable IPC handler
dom/ipc/PContent.ipdl:1609 IPDL message definition (no restrictions)
dom/base/Document.cpp:1646-1661 Legitimate caller (content-side)
dom/webidl/Document.webidl:317-320 WebIDL gate (content-side only, irrelevant to compromised renderer)
ipc/glue/TransportSecurityInfoUtils.cpp:45-76 Cert IPC serialization (raw DER)
security/manager/ssl/nsNSSCertificate.cpp:638-658 Cert deserialization (no validation)
security/manager/ssl/nsCertOverrideService.cpp:380-410 Override storage + disk persistence
security/manager/ssl/SSLServerCertVerification.cpp:309-372 HSTS/pin check at connection time (partial mitigation)
Flags: sec-bounty?

PoC: Browser Console script that adds the cert override

Group: firefox-core-security → dom-core-security
Status: UNCONFIRMED → RESOLVED
Closed: 3 months ago
Component: Security → DOM: Content Processes
Duplicate of bug: CVE-2026-16372
Product: Firefox → Core
Resolution: --- → DUPLICATE
Flags: sec-bounty? → sec-bounty-
Flags: sec-bounty? → sec-bounty-
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: