Blocked popup report-index confusion causing UI selection mismatch
Categories
(Toolkit :: Popup Blocker, defect)
Tracking
()
People
(Reporter: t.satoki111, Assigned: mdauer)
Details
(Keywords: csectype-spoof, reporter-external, sec-low, Whiteboard: [client-bounty-form][adv-main151+])
Attachments
(2 files)
Overview
Firefox flattens blocked popups from the entire BrowsingContext tree into one UI list, but replays them using the per-document local popup array stored by each PopupAndRedirectBlockingChild.
As a result, when blocked popups exist across multiple browsing contexts or after a non-zero offset has been introduced, the popup the user selects in browser chrome can differ from the popup that is actually reopened.
Affected area
- Popup-blocker notification menu entries used to reopen individual blocked popups
unblockAllPopups()behind "Allow pop-ups for site" is affected by the same index confusion- Current stable-equivalent source in:
PopupAndRedirectBlockerObserver.sys.mjsPopupAndRedirectBlockingParent.sys.mjsPopupAndRedirectBlockingChild.sys.mjs
Preconditions
- More than one blocked popup exists within a single browser/tab
- At least one target popup belongs to a browsing context with a non-zero flattened offset
- The user chooses a specific blocked popup from the popup-blocker UI
Conditions for exploitation
- Parent-side code builds one flat popup list across all browsing contexts
- UI stores the flat position as
popupReportIndex - Parent-side replay sends that flat index back to a specific child actor
- Child-side replay interprets it as a local index into
state.popups
Reachability
- Blocked popups are recorded in one or more frames/documents
PopupAndRedirectBlockingParent.getBlockedPopups()walks the browsing-context tree and appends popup records into one flat arrayPopupAndRedirectBlockerObserver.onPopupShowingBlockedPopups()assignspopupReportIndex = ifrom the flat array position- The user clicks a menu item
showBlockedPopup()/unblockPopup()forwards that flat index to the actor selected bybrowsingContextPopupAndRedirectBlockingChild.#unblockPopup()usesstate.popups[idx]as a local index
Root cause
This is a global-index vs local-index confusion.
- Parent/UI side:
- popup records are flattened across the whole browser tree
- the stored index is global
- Child side:
- popup state is maintained per document
- replay expects a local index
No conversion from flattened offset to actor-local offset exists in the replay path.
Evidence
1. Flat list creation
firefox/toolkit/actors/PopupAndRedirectBlockingParent.sys.mjsgetBlockedPopups()traverses all browsing contexts and appends popup data into oneresult
firefox/browser/modules/PopupAndRedirectBlockerObserver.sys.mjsonPopupShowingBlockedPopups()stores loop indexiaspopupReportIndex
2. Local replay
firefox/toolkit/actors/PopupAndRedirectBlockingParent.sys.mjsunblockPopup()selects one actor usingbrowsingContextand forwardsindex: aPopupIndex
firefox/toolkit/actors/PopupAndRedirectBlockingChild.sys.mjs#unblockPopup()resolvesthis.#getOrCreateDocState().popups[idx]
3. Missing normalization
- No flat-index to local-index conversion exists
- No per-context base offset is stored in the menu item
Falsifiability
This candidate would be weakened only if another path normalized the index before it reached the child actor, but that normalization does not exist in:
PopupAndRedirectBlockerObserver.showBlockedPopup()PopupAndRedirectBlocker.unblockPopup()PopupAndRedirectBlockingChild.#unblockPopup()
The effect is not always "open the wrong popup". Depending on offsets, it can also be a no-op.
- Let
kbe the number of blocked popups that appear earlier in the flattened list - Let
jbe the target popup's local index in its own document - The child receives
k + j - If
k + j < localPopupCount, a different popup in the same document is reopened - Otherwise, replay resolves to
undefinedand nothing opens
So exploit reliability depends on popup distribution, but the index confusion itself is directly supported by code.
Why this is not acceptable behavior
The popup-blocker menu is a security-sensitive approval UI. It lets the user choose which blocked popup URL to allow. If the chosen menu item does not map 1:1 to the reopened popup, then browser chrome is misrepresenting the security decision the user is making.
That exceeds a mere functional bug or display issue.
Example abuse scenario
One plausible setup:
- Hidden iframe A triggers one blocked popup
- iframe B triggers two blocked popups
- The UI shows iframe B's first popup as a benign-looking URL
- The user selects that menu item
- Because iframe B's entries are offset in the flattened list, the browser reopens iframe B's second popup instead
This lets an attacker exploit the user's trust in the browser's popup-selection UI.
Difference from prior cases
- Older popup-blocker cases focused on wrong site attribution or wrong restoration context
- This candidate is a modern implementation bug caused by flattening popup records across
BrowsingContexts while replay still uses per-document local state
Duplicate check
- Existing
Exploitcontent only containedlogi_homepage_pipe_split_js - No same-root-cause entry was found there
- Family overlap exists with historical popup logic bugs, but not the same implementation cause
Safe reproduction
- Create a test page with multiple iframes in one tab
- From different frames, trigger multiple blocked
window.open(...)attempts - Open the popup-blocker menu
- Compare each displayed popup URI with the URI that actually reopens
Use only self-controlled test URLs.
Mitigation / fix direction
- Store
{ browsingContext, innerWindowId, localPopupIndex }in each menu item instead of a flattened index - Or keep the flat list but explicitly preserve the local index for the source actor
- Fix
unblockAllPopups()to recompute local indices per browsing context instead of reusing flat indices
Bounty relevance
- Logic bug, not memory corruption
- Not a mere crash
- Does not require prior content compromise
- Arises during ordinary popup-blocker UI handling
This makes it a reasonable Mozilla client bug bounty report candidate as a browser-chrome security-decision confusion issue.
Chain Attack: Popup Confusion + Permission Request Escalation
The popup index confusion can be chained with permission requests for higher impact:
Attack Flow
- Attacker page embeds hidden iframes that trigger blocked popups
- iframe A triggers a popup to
safe-page.html(benign URL shown in UI) - iframe B triggers a popup to
attacker-permissions.html(requests camera/mic/geolocation) - User clicks the popup blocker and selects the "Safe Page" entry
- Due to index confusion,
attacker-permissions.htmlopens instead - Critical: The user's "Allow" click on the popup blocker provides user activation to the newly opened popup (via
GetUserActivationModifiersForPopup()innsGlobalWindowOuter.cpp:6874) - The wrong popup immediately calls
navigator.mediaDevices.getUserMedia()orNotification.requestPermission() - Because the popup has valid user activation (
mHasValidTransientUserGestureActivation = true), the permission request proceeds without "user gesture required" warnings - A permission prompt appears for the attacker's origin
Why This Works
nsGlobalWindowOuter.cpp:6873-6914: User activation modifiers are retrieved from the browsing context before the popup opensnsContentPermissionHelper.cpp:421-422:mHasValidTransientUserGestureActivationis captured from the document's current state at permission request time- The popup blocker "Allow" click → user gesture → inherited by wrong popup → enables immediate permission request
Impact Amplification
Without the chain: Phishing page opens (UI spoofing only)
With the chain: Permission prompt from wrong origin appears with user activation, potentially granting camera/mic/location to the attacker
Comment 1•6 months ago
|
||
The popup-blocker menu is a security-sensitive approval UI.
It's really more of an anti-abuse spam blocker, but if we're going to do it we shouldn't confuse our links. Assuming the user even wants to open a popup from a page that doesn't know how to do acceptable popups -- that's already suspicious behavior
Updated•6 months ago
|
Comment 2•6 months ago
|
||
Maurice, you worked on some of this for redirect blocking, do you have a chance to take a look here?
| Assignee | ||
Comment 3•6 months ago
|
||
Thanks for the report! It looks like this has been an issue ever since the popup blocker was introduced.
| Assignee | ||
Comment 4•6 months ago
|
||
Comment 6•5 months ago
|
||
Updated•5 months ago
|
Updated•5 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•2 months ago
|
Updated•1 month ago
|
Description
•