Repository navigation
IframeModal message listener does not validate message source #42490
Description
Description:
In apps/meteor/client/views/marketplace/IframeModal.tsx, the component registers a global window message listener (iframeMsgListener) to handle confirmation or cancellation events from an embedded marketplace iframe.
Currently, the listener parses any message received on window and triggers confirm() or cancel() if the payload has a JSON result field, without validating that the event originated from the modal's specific <iframe>.
As a defense-in-depth / hardening measure, the listener should verify that event.source === iframeRef.current?.contentWindow before handling the message so foreign windows or frames cannot trigger the callback.
Note: Under standard workspace settings, there is no known practical exploit. This is a low-risk hardening improvement to align with best practices for window message handling.
Steps to reproduce:
- In Rocket.Chat, navigate to Marketplace > Explore and select an app that opens an iframe modal (e.g. "WhatsApp 360Dialog (Cloud API)").
- Click Subscribe to mount
IframeModal(displays the "Subscription Details" iframe modal). - Open the browser DevTools Console and dispatch a message from the parent window:
window.postMessage(JSON.stringify({ result: true }), '*');
-
Observe that
iframeMsgListeneraccepts the message without validatingevent.source, treating subscription as confirmed, closing the iframe modal, and proceeding to the "Required Permissions" dialog.

-
Clicking Agree triggers an installation request (
POST /api/apps). However, the server returns a 400 Bad Request ("Invalid url. It doesn't exist or is not 'application/zip'"), as the app was not genuinely licensed on Rocket.Chat Cloud, preventing actual installation.

Expected behavior:
iframeMsgListener must verify that event.source === iframeRef.current?.contentWindow before handling any message event. Messages from other windows, popups (e.g., via window.opener), or foreign frames must be ignored.
Actual behavior:
Because iframeMsgListener does not check event.source or origin, the global window listener accepts and processes any message event containing { result: true }:
-
Cross-Window Message Injection (CWE-346):
Any arbitrary tab or popup holding a window reference (e.g., viawindow.opener.postMessage(...)) can broadcast messages to the Rocket.Chat window while an admin hasIframeModalopen. -
Unmitigated App Request Spoofing (
action === 'request'):
InuseAppInstallationHandler.tsx(lines 98–111), there is no payment gate. A forgedpostMessageimmediately executesnotifyAdmins()with arbitrary attacker-controlled text (postMessage.message), broadcasting spoofed app requests to workspace administrators without user consent or server validation. -
Incompatible Modal Bypass (
useOpenIncompatibleModal.tsx):
Incompatible version warnings and administrative confirmations are dismissed and approved without user interaction. -
Client-Side State Corruption & Premature Permission Prompt:
In purchase flows, receiving{ result: true }setsisPurchased = truein client state and automatically triggers the "Required Permissions" review modal.
(Note on Server Guardrail: When an admin subsequently clicks "Agree" on a spoofed purchase flow, POST /api/apps fails with 400 Bad Request ("Invalid url. It doesn't exist or is not 'application/zip'") because Rocket.Chat Cloud requires a legitimate purchase license before delivering the zip package. While this server-side check prevents unauthorized paid app installation, the client-side origin validation defect, admin notification forgery, and modal flow hijacking remain unmitigated without this fix).
Server Setup Information:
- Version of Rocket.Chat Server: 8.8.0 (Docker) / 8.10.0-develop
- Operating System: Windows / Linux container
- Deployment Method: docker
Client Setup Information:
- Desktop App or Browser Version: Google Chrome 134 / Brave
- Operating System: Windows 11
Additional context:
- Why validate
event.sourceinstead of strictevent.origin?
Marketplace app purchases and external request flows may undergo third-party OAuth or multi-domain payment redirects. Validatingevent.source === iframeRef.current?.contentWindowensures that incoming events strictly originate from the modal's specific DOM iframe, completely blocking external browser tabs, popups, and sibling frames while preserving redirect compatibility.
Activity