Widget shim postMessage origin bypass via user-controlled appName
medium
CVSS 3.1: 4.3 · Asset: widget.simulator.company/shim.js
- Severity: Medium (depends on what
the downstream
_e(namespace, actorId, type, payload)handler does with attacker-controlled input) - CVSS 3.1:
AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N→ 4.3 (conditional) - CWE: CWE-20 (Improper Input Validation), CWE-346 (Origin Validation Error), CWE-942 (Overly Permissive Cross-domain Whitelist)
- Asset:
https://widget.simulator.company/shim.js— loaded intoadmin.corezoid.comcontext by theeditor_aiwidget (/system/conf→web_settings.widgets.editor_ai.src) - Discovered: 2026-04-26
- Status: Open — requires follow-up to determine the
expected
appNamevalue and downstream handler behavior
Summary
The shim script at widget.simulator.company/shim.js —
loaded inside admin.corezoid.com per its CSP — registers a
message event listener with a weak origin check
that can be bypassed by user-controlled data:
addEventListener("message", function(r) {
if (!!r && !!r.data && !!r.data.type) {
var a = r.origin === fe.origin, // proper origin check
i = r.data.appName === s, // bypass: attacker controls data.appName
n = a || i, // ← OR means EITHER sufficient
o = Boolean(e[r.data.type] || t[r.data.type]);
if (n && o) {
var l = r.data, u = l.namespace, d = l.actorId,
f = l.type, c = l.payload, h = void 0===c ? {} : c,
g = h.isLauncher;
_e(u, d, f, h, void 0 !== g && g); // invoke handler with attacker-controlled data
}
}
}, !1);
The i = r.data.appName === s check compares the
appName field of the incoming message against a local
variable s. Because r.data.appName comes from
the message payload itself, an attacker can set it to match
s — effectively passing the check without the real
origin matching.
The logical flaw is n = a || i: if EITHER
origin check OR appName check passes, the
message is processed. A proper implementation would be
n = a && i (both must hold) or — more correctly —
n = a (only trust by origin).
Attack path
- Victim (Corezoid admin user) is logged into
admin.corezoid.com. - Victim opens an attacker-controlled page in another tab, or admin is
embedded in an attacker-controlled iframe (mitigated if admin sets
X-Frame-Options: DENY— it setsSAMEORIGIN, so a cross-origin iframe cannot frame admin). - Attacker's page opens
admin.corezoid.comin a popup window or iframe (if possible), OR useswindow.postMessageon a reference it obtains. - Attacker sends
postMessage({appName: "<guessed-or-leaked-s-value>", type: "<known-type>", namespace: "...", actorId: "...", payload: {...}}, "*")at the admin-context shim. - The shim processes the message and invokes
_e(namespace, actorId, type, payload)— the behavior of_edetermines impact.
Unknowns requiring follow-up
To establish exact severity:
- What is the
svalue? — the expectedappName. Needs either:- Dynamic analysis (debug the shim in a live browser session, read the variable at runtime)
- Deeper static analysis (trace through ~1.87MB of minified code to
find where
sis assigned — the minifier may have renamed it) - Or the iframe/parent that legitimately sends these messages may have the string visible in its own code
- What does
_e(namespace, actorId, type, payload)do? — does it:- Just render UI (low impact)?
- Call admin.corezoid.com APIs with attacker payload (authenticated-action CSRF bypass)?
- Modify the DOM (potential DOM XSS)?
- Invoke
eval/Function(direct JS execution)?
I did not complete these items to stay within the conservative-scan RoE (running instrumented browser against production + longer bundle reverse would extend the engagement; I flagged the finding and stopped).
Evidence
/tmp/shim.js(1.87MB React bundle, publicly fetchable viacurl https://widget.simulator.company/shim.js)- Message handler starts at byte offset ~50524 (see
grep -oE 'addEventListener("message"output) - The
postMessagevulnerability is a static-analysis finding from the cleartext JS
Impact
If the downstream _e(...) handler performs any
admin-privileged action or renders attacker-controlled content, this
becomes effective cross-origin access to an
authenticated admin session — bypassing SOP without needing a real
XSS.
Common payloads for widgets accepting postMessage:
- Insert markup into the editor → stored XSS in the workflow description
- Trigger API calls the victim is authorized for → CSRF with full attacker control
- Read data from iframes → SOP leak
Remediation
Priority 1 — fix the origin check:
addEventListener("message", function(r) {
// Strict origin check only — remove the appName bypass
if (r.origin !== fe.origin) return;
// ... proceed with safe handling
}, !1);
Or if multiple trusted origins need to be supported, maintain an allowlist of origins:
var ALLOWED = [fe.origin, "https://admin.corezoid.com", "https://sim.simulator.company"];
if (!ALLOWED.includes(r.origin)) return;
Never use message payload fields for trust
decisions. The entire point of event.origin is
that it is set by the browser and cannot be forged; using a payload
field (appName) instead of the origin is substituting a
strong authentication for a weak one.
Priority 2 — defense in depth:
- Drop any embedded-widget feature if it's not heavily used. The "editor_ai" widget is one admin UI enhancement; if its value is low, removing the integration simplifies the attack surface.
- Scope the shim to read-only operations only — never let postMessage trigger a state-changing API call.
- Audit
_ehandler (the downstream function) for injection sinks. - Consider using the
MessageChannelpattern instead of broadcastpostMessage— this provides a dedicated port with implicit origin pinning.
References
- CWE-20, CWE-346, CWE-942
- OWASP — DOM-based XSS / postMessage security
- MDN —
Window.postMessage()security concerns - Securing cross-origin communicators