Closed Bug 1940116 (CVE-2026-8960) Opened 1 year ago Closed 6 months ago

Content of Permission Prompt Can Be Spoofed, Because Extension Page/Browser Action Popup Can Cover Permission Prompt

Categories

(WebExtensions :: General, defect, P2)

defect

Tracking

(firefox-esr115 wontfix, firefox-esr140 wontfix, firefox149 wontfix, firefox150 wontfix, firefox151+ fixed)

RESOLVED FIXED
151 Branch
Tracking Status
firefox-esr115 --- wontfix
firefox-esr140 --- wontfix
firefox149 --- wontfix
firefox150 --- wontfix
firefox151 + fixed

People

(Reporter: canalun, Assigned: robwu)

References

(Depends on 1 open bug)

Details

(Keywords: csectype-spoof, sec-low, Whiteboard: [client-bounty-form][addons-jira][adv-main151+])

Attachments

(4 files, 1 obsolete file)

Attached file poc.zip —

Summary

The content (i.e. domain and permission) of site's permission prompt can be spoofed, because extension's page/browser action popup can cover (i.e. render over) the prompt.

The attacker has to create two things.

  • an extension with well-crafted popups
  • a page on that the attacker wants the user to allow unintentional permission

Description

  • Because the layer order of rendering between extension's page/browser action popup and site's permission prompt is determined by the rendered order, one which is rendered first can cover the other.
  • So, a malicious extension creator can spoof the content (i.e. domain and permission) on permission prompt, by crafting extension's page/browser action popup and fitting it to the prompt.
  • Here, in order to fit the popup to the prompt, the attacker (equal to the malicious extension creator) has to do two things.
    A. make the user unknowingly open the popup, after the prompt is rendered
    B. resize the window (B is unnecessary in some cases. please refer to Note 3)
  • For achieving A, the attacker can use "_execute_page_action" and "_execute_browser_action" of manifest.json. It enables the user to open the popups with keyboard shortcut.
    • Note 1: Although I couldn't find how to use browser.browserAction.openPopup on FireFox, if the attacker can use it, opening the browser action popup is not only limited to keyboard shortcut but also achieved by user gesture such as click.
  • For achieving B, the attacker can create popup window and use window.resizeTo.
    • Note 2: Browser action popup exists only on the main window (not on a popup window). So the attacker cannot use it if using a popup window.
    • Note 3: Creating a popup window consumes one user gesture, so the attacker has a choice to use the main window. In this case, the spoofing should succeed only if the main window is narrow enough. So, the attacker may instruct the user to shrink the window size somehow.
  • The attacker can spoof the permission prompt on a malicious page and the user can give the malicious domain unintentional permission.

Demo and Reproduction Steps

I think it's best to see the demo movie at first to know what happens, because it depends on the screen size if the popup fits the prompt, i.e. if the spoof succeeds.
(Note 4: I haven't tried to programmatically modify the popup size according to the screen size and even I don't know if it's possible. However, I can research on that if that is an important point.)

The demo is produced by the following reproduction steps.
It uses the page action popup and resizes a popup window.

  • Install the attached extension (i.e. manifest.json, content.js and page_action.html). Just fyi, I installed it as a temporary extension.
  • Host the attached index.html and popup.html in the same directory.
  • Access to index.html.
  • Press the displayed button, which opens a popup window.
  • Following the instruction on the popup window, press Cmd+Shift+I.
  • Observe the spoofing succeeds.

Environment

  • FireFox version: 133.0.3 (aarch64)
  • OS: macOS Sequoia 15.2

Possible Mitigation

I think one of the simple solutions is "make the permission prompt always rendered on the most top (or at least upper than extension's popups)". Actually, this spoof was once reported on Chromium (ref: https://issues.chromium.org/issues/40058873) and Chromium seems to have taken this solution. Comment #13 may be informative (ref: https://issues.chromium.org/issues/40058873#comment13).

Flags: sec-bounty?

I attach the demo movie on this comment.

Component: Security → Site Permissions
Keywords: csectype-spoof

I don't know if this should be in Site Permissions or a WebExtensions component. It looks like the Chromium fix was applied to what looks like UI code for WebExtension popups.

Severity: -- → S3
Priority: -- → P3

I'm moving this to WebExtensions, because this is not unique to the Site Permissions prompts.

A similar spoofing issue also exists with the extension installation prompt. In that case, because the prompt is anchored at the right, a PoC would have to use a browser_action instead of a page_action to be more visually convincing.

Currently, to abuse this, the extension has to trick the user into triggering a user interaction. The PoC relies on an extension-registered shortcut for that. Chrome already supports an API to open the popup without further user interaction, but we do not support that yet. We should fix this bug before supporting openPopup() without user gesture (bug 1799344).

I'm wondering what we should do. There are many options, not mutually exclusive:

  • Close existing prompts when the popup is triggered.
  • Suppress or dismiss prompts when the popup is open.
  • Disable action buttons when there is something else in front of the prompt. We already have similar protections when there is a popup in front of the window.
Blocks: 1799344
Severity: S3 → --
Status: UNCONFIRMED → NEW
Component: Site Permissions → General
Ever confirmed: true
Priority: P3 → --
Product: Firefox → WebExtensions

I discussed this with my team and UX today. Next step is to look into the feasibility of rendering the extension popup panels (page_action and browser_action) behind all other doorhangers and permission prompts.

Thank you for sharing the situation. Please tell me if I could help anything.

(In reply to Rob Wu [:robwu] from comment #4)

I discussed this with my team and UX today. Next step is to look into the feasibility of rendering the extension popup panels (page_action and browser_action) behind all other doorhangers and permission prompts.

That's bug 1901806 which seems to be quite tricky. Fixing that would fix a whole class of issues.

The severity field is not set for this bug.
:willdurand, could you have a look please?

For more information, please visit BugBot documentation.

Flags: needinfo?(wdurand)

There is no obvious solution to this problem yet. Nevertheless, we'll schedule some time to try and find a decent solution here.

Assignee: nobody → rob
Severity: -- → S3
Flags: needinfo?(wdurand)
Priority: -- → P2
Whiteboard: [client-bounty-form] → [client-bounty-form][addons-jira]

Ideas to explore:

Other random ideas:

  • Move/render the prompts at a place where there is space on the screen.
  • A simpler version is to anchor the popup panel to the right of the extension button to which the extension panel is anchored. This would prevent the panel from overlapping with the existing browser window. It could cover content from the other window, but that case should already be handled by the existing window focus logic (preventing interaction with prompt buttons when another window was focused).

changing the security severity to be in line with the Chromium bug severity.

Keywords: sec-moderate → sec-low

The extension is clearly being abusive here, and usually one of our best defenses is to make it clear who is doing the abuse. As a more general defense apart from this specific spoof, maybe extensions shouldn't be able to create these anonymous-looking panels.

  • In the specific POC here the extension-created panel is anchored to the ellipsis icon shown when the window gets narrow. What if that icon was replaced by the add-on's icon (with the add-on name in hover text) while its panel was open, and revert to the ellipsis when it's closed? This POC might still fool some victims, but the ones who weren't fooled would have a much easier time reporting which add-on was to blame.
  • I'd like to see all extension panels have a title strip with the name of the extension. That way they can't possibly be confused with native browser UI or try to hide as an overlay, and again people would have a much easier time figuring out who to blame for abuse. Anchoring to the button might be good enough if people have just explicitly clicked that button, but it's much less clear when panels are opened in response to hot-keys or other events.
See Also: → 1954376
Duplicate of this bug: 1954376

Baku is going to take over this work; the approach to take is stated at the top of comment 9. Basically ensuring that the extension action panel is mutually exclusive with prompts. The exact prompts requires an audit of the code base.

This blocks bug 1799344.

Assignee: rob → amarchesini
Status: NEW → ASSIGNED

Thank you for working on it.

This is a mass bug change for bug bounty purposes. GUID for this change to
search/archive on: c29ab022-4839-4706-86ca-770ef07c519c

Flags: sec-bounty? → sec-bounty+
See Also: → 1964491
Attached file (secure) (obsolete) —

Comment on attachment 9487720 [details]
(secure)

This patch moved to bug 1967196, specifically https://phabricator.services.mozilla.com/D249959

Attachment #9487720 - Attachment is obsolete: true
Depends on: 1967196

baku implemented a fix in bug 1967196; I'll verify whether this sec bug has been fully resolved.

Assignee: amarchesini → rob
Attached file action.openPopup.zip —

Test case that I used to verify behavior. While testing this, I noticed that although the WebRTC prompt is no longer overlapping, that other prompts such as the notifications API still do.

STR:

  1. Load attached extension in about:debugging
  2. Try various combinations of the button and/or checkbox.

Expected:

  • There should not be any chance for the extension popup panel and other notifications to overlap.

(In reply to Rob Wu [:robwu] from comment #19)

While testing this, I noticed that although the WebRTC prompt is no longer overlapping, that other prompts such as the notifications API still do.

According to baku, this is the expected behavior right now, and that we should add the queue attribute on the extension panel to resolve this issue.

I had an open question - what if an extension is triggering a prompt from the popup panel?
I tested this with the WebRTC and Notifications prompts - these do currently not appear.
The extension permission prompt does currently show (anchored to the extension button).

Even after setting the queue attribute on the panel at https://searchfox.org/mozilla-central/rev/1838f847aa3bf909c3d34a94a8f0cd7e37fca086/browser/components/extensions/ExtensionPopups.sys.mjs#524, the difference is now:

  • the extension permission prompt is still shown, but renders behind the popup (increasing the spoofing concern!)
  • the notifications permission prompt is still visible at the same time as the extension popup.

In either case (with and without the queue), I also observe the following:

  • When the WebRTC permission prompt is shown, and the extension popup is shown (via the shortcut in the STR), the WebRTC popup is hidden. Clicking anywhere else closes the extension popup and immediately reveals the WebRTC permission prompt, which raises clickjacking concerns, e.g. by instructing the user to double-click somewhere.
  • When I tried to double-click on the area where I expect the Allow button in the WebRTC prompt, the click does not register, and the global Browser Console contains the following message: PopupNotifications._onButtonEvent: Button click happened before the security delay: 241.60816666670144ms
  • This delay appears to be 500ms, which feels relatively short. On the other hand, extensions can already try to position windows on top of regular browser UI and change the window focus, so this is not unique to the action.openPopup API.

needinfo baku to look into observations from comment 20 (+test in comment 19).

Flags: needinfo?(amarchesini)

(In reply to Rob Wu [:robwu] from comment #20)

I had an open question - what if an extension is triggering a prompt from the popup panel?
I tested this with the WebRTC and Notifications prompts - these do currently not appear.

Someone else encountered this issue too and filed this as bug 1982832.

See Also: → 1982832

baku and I met to discuss the mitigation proposed in bug 1967196, which introduced a "queue" flag with two effects when set on a panel (also tested in comment 20):

  • the panel is not shown until others in the queue have shown (non-queueable items are shown immediately).
  • as long as the panel is shown, the display of other queued panels is deferred (non-queueable panels dismiss queued panels that have already been shown).
  • (non-queueable popup panels, which is almost every prompt are ignored and continue to show when a queueable popup is opened)

This attempts to mitigate the spoofing risk by ensuring that only one panel/prompt is shown at any time. The browserAction popup panel does currently not have the "queue" attribute set, but if it did, then the popup panel would not open if the user clicks on the extension button (or any of the other user interactions, such as triggering a shortcut as shown in the PoC of this bug), which would be a severe functional regression.

The next step after the meeting was to determine the most feasible path forwards. Here is a write-up of relevant aspects and some options.

For comparison, Chrome's behavior with multiple panels is as follows:

  • Extension popup always renders behind any permission prompt.
  • When action.openPopup() is called, the popup opens immediately, but rendered behind permission prompts.
    • Except for extension permission prompts; if there is one open, Chrome rejects with the following error (it is not clear whether this is intended behavior, the source of the error is not directly connected to the observed error message):

      Cannot show popup for an inactive window. To show the popup for this window, first call chrome.windows.update with focused set to true.

That behavior makes sense to me, as it gets rid of the spoofing concerns. However, the platform does not have support for setting the z-index of panels (bug 1901806).

To make progress on this bug without platform support, we need alternatives. Here are some options with minimal dependencies:

  • Option "queue": Set queue attribute on extension panel.
    • As mentioned above, a downside is the potential delay when the panel is postponed for an unbounded amount of time.
    • A downside is that other (queueable) prompts are suppressed while the extension panel is open (like bug 1982832).
    • To limit the regression potential, we could skip setting the queue when there is a high level of certainty about the user intentionally interacting with the button. The most common case (clicking the extension button) can be exempted, but it would be less obvious for other "user interactions" such as context menu clicks or keyboard shortcuts (the latter is demonstrated in the PoC of this bug). Even if we add the behavior to action.openPopup() only (to unblock bug 1799344), it would still imply the need to have a follow-up bug to relax the strict "one popup at a time" constraint.
  • Option "ugly UI": detect the presence of open popups/prompts, and render a prominent border around it, to mitigate the spoofing risk as highlighted in this bug.
    • This does not look great, but reduces the room for confusion/UI spoofing, with minimal chance of (non-visual) regressions. Extensions can already open popup windows with custom dimensions (width/height) and position on the screen, so it could be a reasonable mitigation if the distinctiveness of the popup panel at least meets that bar (and clickjacking protections continue to be present, see end of comment 22).
  • Option "error": detect the presence of open popups/prompts, and refuse to open the popup if other popups are available.
    • Downside: the popup may unexpectedly not appear.
    • Downside: extensions have no way of knowing which popup/prompts are open, nor a way to dismiss these blockers.
  • Option "dismiss": detect the presence of open popups/prompts, and dismiss them when the extension panel is open (and allow other prompts to render on top of the panel).
    • This could result in the dismissal of "important" prompts, but then again this is a capability that extensions already have (they can close/open arbitrary browser windows). It is also a behavior that permission prompts already have (e.g. visit permission.site and click on buttons; only one permission prompt is shown at a time)
  • Option "reshow": detect the presence of open popups/prompts, and hide / show them again after the extension panel is opened, to simulate the extension panel being rendered behind other panels. The resulting panel stacking order would be similar to Chrome.
    • This is a work-around for the unavailability of bug 1901806. On platforms that support z-index, we can skip this "reshow" logic.
    • Downside: unknown implementation complexity. It is unclear if other panels are designed to hide and show temporarily. One reduction in the uncertainty is that at least PopupNotifications already has logic to hide and re-show doorhangers when a window/tab is unfocused and refocused.

The first option is generic due to the use of "queue". The other/last four options depends on the ability to detect the presence of other popups. We could achieve the latter by auditing all instances of all popups/panels (or the common/shared code that shows it). Unlike the generic "queue" approach, moving the responsibility of accounting for other prompts/panels to the extension panel allows us to improve the UX by accounting for the context. For example, we could dismiss permission prompts upon opening the panel, but allow the same kind of prompts to appear on top when called from by an extension.

Flags: needinfo?(amarchesini)

(In reply to Rob Wu [:robwu] from comment #23)

Unlike the generic "queue" approach, moving the responsibility of accounting for other prompts/panels to the extension panel allows us to improve the UX by accounting for the context. For example, we could dismiss permission prompts upon opening the panel, but allow the same kind of prompts to appear on top when called from by an extension.

The downside here would be that every panel/popup consumer would have to implement similar logic to avoid similar conflicts, right? Because it's not just permissions and extensions that have overlapping/clickjacking style problems... That feels very unsatisfactory because it means we will continue playing "whack a mole" every time some new kind of panel/popup is added (and the combinatorial explosion will make this very hard to reason about). Am I missing something there?

Flags: needinfo?(rob)
Flags: needinfo?(rob)

(In reply to :Gijs (he/him) from comment #24)

(In reply to Rob Wu [:robwu] from comment #23)

Unlike the generic "queue" approach, moving the responsibility of accounting for other prompts/panels to the extension panel allows us to improve the UX by accounting for the context. For example, we could dismiss permission prompts upon opening the panel, but allow the same kind of prompts to appear on top when called from by an extension.

The downside here would be that every panel/popup consumer would have to implement similar logic to avoid similar conflicts, right? Because it's not just permissions and extensions that have overlapping/clickjacking style problems... That feels very unsatisfactory because it means we will continue playing "whack a mole" every time some new kind of panel/popup is added (and the combinatorial explosion will make this very hard to reason about). Am I missing something there?

Something unique to extension popup prompts is that third-party code (extensions) can open (when bug 1799344 is fixed, even without user interaction) and close the panel on demand, with precise control over the size and position where the browser windows are shown. Independently of what the other components do, I think that these characteristics mean that it is valuable to do something for extensions now, without being blocked on a one-size-fits-all solution.

(In reply to Rob Wu [:robwu] from comment #25)

Something unique to extension popup prompts is that third-party code (extensions) can open (when bug 1799344 is fixed, even without user interaction) and close the panel on demand, with precise control over the size and position where the browser windows are shown. Independently of what the other components do, I think that these characteristics mean that it is valuable to do something for extensions now, without being blocked on a one-size-fits-all solution.

Isn't the same thing true for select dropdowns? And autofill and form validation popups (though perhaps less control over the exact size, still quite some, based on the size and location of the associated input field) ?

Flags: needinfo?(rob)

Following the above feedback and further discussions with :emz, I'm going to solve the issue in a way different from what I described in comment 23.

My current plan is to inform EnableDelayHelper (and similar logic such as what was added in bug 1866661) of the relevant state (visibility and non-visibility to start with, maybe also dimensions), so that these existing clickjacking protections (which are currently window-based) are aware of same-window UI that overlaps with permission notifications etc (such as browser action popup panel from this bug).

As long as the popup panel is visible, the prompts cannot be interacted with (at least not granted to do something dangerous). The browser action popup panel closes when something else is clicked, so if the user clicks anywhere else, the popup (that could potentially spoof the prompts) will close, and after a sufficient amount of time (1 second) has passed, the button is unlocked.

One potential step further behind "popup shown = ignore any click" is to also account for any potential overlap between popup panels and the prompts, to avoid over-eagerly blocking clicks when the prompt click area is at a large distance away from the browser action popup panel. This is however a bit more complex to implement, as it requires the generic helpers to be aware of the dimensions and offsets of all involved popup panels/buttons and browser action popups, as well as updates to size changes. Moreover, additional complexity follows from things within the popup panel that could stick out (e.g. dropdowns). For these reasons, I'm proceeding with the simplest approach, which is to activate the click jacking protections for as long as the browser action popup panel is open (and a bit after it).

Flags: needinfo?(rob)
Depends on: 1949414
Duplicate of this bug: 2019667
Duplicate of this bug: 2019844
Depends on: 2022281

Update: With this bug in mind, I updated action.openPopup() in bug 2022281, to reject the API call if another panel or menu is open. This being a new API (at least in relation to the user gesture requirements, the API already existed before when a user gesture was involved), starting stricter now would make it easier to prevent extensions from relying on openPopup() to cover browser UI. While this works for floating UI (menus, panels, etc.), it does not for UI that do not always render on top when shown, such as the notifications toolbar (used for the protocol handler prompt, for example).

While testing locally (e.g. clicking on "Protocol Handler" at https://permission.site/ ), I noticed that many of the clickjacking logic rely on the "focus" event to detect whether a window is freshly focused and needs a reset. With extension popups, this "focus" event appears to be fired (when the focus shifts from the <browser> of the extension popup to the tab's <browser>). However, that focus change is only triggered when the extension popup is fully hidden. This is too late, because any user click would be processed before the focus listeners triggers clickjacking protections. A potential solution here could be to shift the focus as soon as the popup is closing.

Another alternative is to set the consumeoutsideclicks attribute on the <panel> containing the extension popup browser. This captures clicks outside the extension popup and prevents the click from propagating elsewhere (consumeoutsideclicks handled by nsXULPopupManager::RollupInternal. While this flag sounds appealing for its simplicity, a potential downside is that it also swallows clicks on UI elements such as a browser tab (preventing users from switching tabs on the first click). If we pursue this path, we should also make sure to avoid setting it if ui.popup.disable_autohide is true (since the popup would not close if this pref is set, for debugging purposes).

No longer blocks: 1799344
Attached file (secure) —
Group: firefox-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 6 months ago
Resolution: --- → FIXED
Target Milestone: --- → 151 Branch

The patch landed in nightly and beta is affected.
:robwu, is this bug important enough to require an uplift?

For more information, please visit BugBot documentation.

Flags: needinfo?(rob)

I'll let this ride the train because the countermeasure for this sec-low security issue has a chance to be annoying/surprising to users, so I want to maximize baking time.

Flags: needinfo?(rob)
Attachment #9487720 - Attachment description: Bug 1940116 - Introduction of a Popup queue, → (secure)
Attachment #9487720 - Attachment is obsolete: false
Blocks: 1982832
See Also: 1982832 →
Attachment #9487720 - Attachment is obsolete: true
QA Whiteboard: [sec] [qa-triage-done-c152/b151]
Whiteboard: [client-bounty-form][addons-jira] → [client-bounty-form][addons-jira][adv-main151+]
Alias: CVE-2026-8960
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: