Skip to content

IframeModal message listener does not validate message source #42490

Open

Description

@electromist

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:

  1. In Rocket.Chat, navigate to Marketplace > Explore and select an app that opens an iframe modal (e.g. "WhatsApp 360Dialog (Cloud API)").
  2. Click Subscribe to mount IframeModal (displays the "Subscription Details" iframe modal).
  3. Open the browser DevTools Console and dispatch a message from the parent window:
    window.postMessage(JSON.stringify({ result: true }), '*');
Image
  1. Observe that iframeMsgListener accepts the message without validating event.source, treating subscription as confirmed, closing the iframe modal, and proceeding to the "Required Permissions" dialog.
    Image

  2. 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.
    Backend returns 400 Bad Request on install

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 }:

  1. Cross-Window Message Injection (CWE-346):
    Any arbitrary tab or popup holding a window reference (e.g., via window.opener.postMessage(...)) can broadcast messages to the Rocket.Chat window while an admin has IframeModal open.

  2. Unmitigated App Request Spoofing (action === 'request'):
    In useAppInstallationHandler.tsx (lines 98–111), there is no payment gate. A forged postMessage immediately executes notifyAdmins() with arbitrary attacker-controlled text (postMessage.message), broadcasting spoofed app requests to workspace administrators without user consent or server validation.

  3. Incompatible Modal Bypass (useOpenIncompatibleModal.tsx):
    Incompatible version warnings and administrative confirmations are dismissed and approved without user interaction.

  4. Client-Side State Corruption & Premature Permission Prompt:
    In purchase flows, receiving { result: true } sets isPurchased = true in 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.source instead of strict event.origin?
    Marketplace app purchases and external request flows may undergo third-party OAuth or multi-domain payment redirects. Validating event.source === iframeRef.current?.contentWindow ensures 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions